@openchambery/web 1.19.17-beta.2 → 1.19.17-beta.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/AgentAvatar-CUDJ3_nZ.js +1 -0
- package/dist/assets/AssistantView-CDXE0eTS.js +3 -0
- package/dist/assets/DiagramView-BrRV4o5T.js +1 -0
- package/dist/assets/FadeInOnReveal-BLSnyG2Z.js +1 -0
- package/dist/assets/Icon-C9ZHbwqp.js +1 -0
- package/dist/assets/MarkdownRenderer-CAdfUgV1.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-BJd1Vjsc.js → MarkdownRendererImpl-D2dIlX0h.js} +2 -2
- package/dist/assets/MarkstreamRendererImpl-Drb2Tpv-.js +3 -0
- package/dist/assets/MobileDetailNavigation-BgCoDqtp.js +1 -0
- package/dist/assets/MobileShareBridge-ZlVURYWR.js +4 -0
- package/dist/assets/MobileSurface-DhLOTW0-.js +2 -0
- package/dist/assets/MultiRunWindow-BCvcwItK.js +1 -0
- package/dist/assets/OnboardingScreen-DSyZtiFS.js +1 -0
- package/dist/assets/OverlayScrollbar-C7fAOAKT.js +2 -0
- package/dist/assets/SettingsView-D57amZJb.js +25 -0
- package/dist/assets/SettingsWindow-B5DqLCkD.js +1 -0
- package/dist/assets/TerminalView-2_RFPaVs.js +8 -0
- package/dist/assets/{ToolOutputDialog-BQ_oXyS_.js → ToolOutputDialog-CQs_7KYT.js} +4 -4
- package/dist/assets/{appThemeRegistry-BDrIDWKH.js → appThemeRegistry-DpLe_jnW.js} +1 -1
- package/dist/assets/button-bhVnVYB_.js +1 -0
- package/dist/assets/collapsible-CoWJB_Zl.js +1 -0
- package/dist/assets/context-menu-gSO0b3sQ.js +1 -0
- package/dist/assets/diagram-Dx4UOZUn.js +1 -0
- package/dist/assets/dialog-C2Dw0yqA.js +1 -0
- package/dist/assets/{es-BQb6I8gl.js → es-Bxd4Lyu9.js} +2 -2
- package/dist/assets/{fileMentionTouchSelection-DzdgAvGS.js → fileMentionTouchSelection-CmQthb6Q.js} +2 -2
- package/dist/assets/{fr-BjGouC_l.js → fr-DiLDwVZs.js} +2 -2
- package/dist/assets/{gitApi-pLkl_IyQ.js → gitApi-8HxdR1zx.js} +3 -3
- package/dist/assets/input-8Te_vPwB.js +1 -0
- package/dist/assets/insertionBoundaries-Cdv9-1Bg.js +1 -0
- package/dist/assets/{ja-Cw9zliYY.js → ja-CWRlmEJM.js} +2 -2
- package/dist/assets/{ko-Dyz0Dk9b.js → ko-DmIugkgj.js} +2 -2
- package/dist/assets/main-CQY7JUX4.js +169 -0
- package/dist/assets/main-Cfru2-qr.js +2 -0
- package/dist/assets/{markdownHeightCache-BL8CUQ0M.js → markdownHeightCache-CT2d4yTV.js} +1 -1
- package/dist/assets/miniChat-BphFgAb_.js +2 -0
- package/dist/assets/mobile-BVRPTuLT.js +2 -0
- package/dist/assets/multirun-CYdieVzW.js +4 -0
- package/dist/assets/{pl-C7EY-dTH.js → pl-BU9AmdUM.js} +2 -2
- package/dist/assets/projectMeta-D8hiy3Dk.js +3 -0
- package/dist/assets/{pt-BR-B1uHpjFW.js → pt-BR-DQmEF9QB.js} +2 -2
- package/dist/assets/renderElectronMiniChatApp-CVqd3Nmc.js +2 -0
- package/dist/assets/renderMobileApp-DgtCGRKP.js +28 -0
- package/dist/assets/{runtimeConfig-BD1Fo5N5.js → runtimeConfig-DXXOBrK_.js} +1 -1
- package/dist/assets/runtimeEndpointReset-bSVOt7pi.js +147 -0
- package/dist/assets/sessionChangeSummary-CfnRZ0gS.js +2 -0
- package/dist/assets/sessionLookup-BZuujM6l.js +1 -0
- package/dist/assets/sortable-tabs-strip-BmvLnn6O.js +1 -0
- package/dist/assets/src-C_Yn9Ylz.css +2 -0
- package/dist/assets/{store-ClnJdJSj.js → store-BoixGrYS.js} +3 -3
- package/dist/assets/textarea-DRL916s6.js +1 -0
- package/dist/assets/tooltip-BLznq5KR.js +1 -0
- package/dist/assets/{uk-DgG8v2md.js → uk-Dax66sAC.js} +2 -2
- package/dist/assets/{useAppFontEffects-CN_3Jty7.js → useAppFontEffects-DhEfjuPp.js} +43 -43
- package/dist/assets/useDesktopWindowControlsLayout-BhNFU2Wd.js +1 -0
- package/dist/assets/{useEffectiveDirectory-C5Gg7EvP.js → useEffectiveDirectory-BhhzFJgX.js} +1 -1
- package/dist/assets/useMobileNavigationStore-1jdHeA49.js +413 -0
- package/dist/assets/useSessionAutoCleanup-C8u2-jrO.js +2 -0
- package/dist/assets/{useWorkerHighlightedLines-BSQQkZaO.js → useWorkerHighlightedLines-DFXK66lA.js} +2 -2
- package/dist/assets/vendor-zod-Dzk5kdUU.js +39 -0
- package/dist/assets/{zh-CN-C6OFKjeT.js → zh-CN-Du4VC8Z0.js} +2 -2
- package/dist/assets/{zh-TW-BVP99aMr.js → zh-TW-C0bY-Dso.js} +2 -2
- package/dist/index.html +3 -3
- package/dist/mini-chat.html +3 -3
- package/dist/mobile.html +3 -3
- package/package.json +1 -1
- package/server/index.js +51 -0
- package/server/lib/opencode/DOCUMENTATION.md +1 -0
- package/server/lib/opencode/core-routes.js +2 -0
- package/server/lib/opencode/feature-routes-runtime.js +20 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +10 -0
- package/server/lib/opencode/routes.js +14 -1
- package/server/lib/opencode/settings-helpers.js +3 -0
- package/server/lib/opencode/settings-helpers.test.js +12 -0
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/question-auto-delegate/DOCUMENTATION.md +124 -0
- package/server/lib/question-auto-delegate/core.d.ts +255 -0
- package/server/lib/question-auto-delegate/core.js +1372 -0
- package/server/lib/question-auto-delegate/core.test.js +672 -0
- package/server/lib/question-auto-delegate/routes.js +223 -0
- package/server/lib/question-auto-delegate/routes.test.js +180 -0
- package/server/lib/question-auto-delegate/runtime.js +243 -0
- package/server/lib/session-goal/DOCUMENTATION.md +12 -2
- package/server/lib/session-goal/runtime.js +138 -35
- package/server/lib/session-goal/runtime.test.js +192 -0
- package/dist/assets/AgentAvatar-m1jNq4bX.js +0 -1
- package/dist/assets/AssistantView-DZMTfY1i.js +0 -3
- package/dist/assets/DiagramView-Byf2evGV.js +0 -1
- package/dist/assets/FadeInOnReveal-UGwClbL_.js +0 -1
- package/dist/assets/Icon-D1BcR8Tz.js +0 -1
- package/dist/assets/MarkdownRenderer-YMePGw0Z.js +0 -2
- package/dist/assets/MarkstreamRendererImpl-2oZKPfgd.js +0 -3
- package/dist/assets/MobileDetailNavigation-BeNUCxzK.js +0 -1
- package/dist/assets/MobileShareBridge-3tBtXXJJ.js +0 -4
- package/dist/assets/MobileSurface-ppn0zIpb.js +0 -2
- package/dist/assets/MultiRunWindow-CItY5bZ2.js +0 -1
- package/dist/assets/OnboardingScreen-bIWeMLVx.js +0 -1
- package/dist/assets/OverlayScrollbar-Bahg_P3r.js +0 -2
- package/dist/assets/SettingsView-0-Yqdzh-.js +0 -25
- package/dist/assets/SettingsWindow-1jOWzU2A.js +0 -1
- package/dist/assets/TerminalView-BLOCPFz9.js +0 -8
- package/dist/assets/button-9R-WgVMl.js +0 -1
- package/dist/assets/clipboard-GUUNlWIv.js +0 -1
- package/dist/assets/collapsible-Bbz9KiJf.js +0 -1
- package/dist/assets/context-menu-CSyOhRz8.js +0 -1
- package/dist/assets/diagram-NdsZih8P.js +0 -1
- package/dist/assets/dialog-DhMGbF_B.js +0 -1
- package/dist/assets/input-Cd_1HyQv.js +0 -1
- package/dist/assets/insertionBoundaries-DJvMDCJJ.js +0 -1
- package/dist/assets/main-B8fZfA3K.js +0 -169
- package/dist/assets/main-BGpPyUV2.js +0 -2
- package/dist/assets/miniChat-DHjphqgR.js +0 -2
- package/dist/assets/mobile-DNMX4u7x.js +0 -2
- package/dist/assets/multirun-BINJ2LCE.js +0 -4
- package/dist/assets/projectMeta-BMkGWqtz.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-BonR3p7C.js +0 -2
- package/dist/assets/renderMobileApp-CZqNs2zK.js +0 -28
- package/dist/assets/runtimeEndpointReset-Dpx6GB_U.js +0 -147
- package/dist/assets/sessionChangeSummary-BG7l2heP.js +0 -2
- package/dist/assets/sessionLookup-BZuvc3Aa.js +0 -1
- package/dist/assets/sortable-tabs-strip-dYW6SH62.js +0 -1
- package/dist/assets/src-CO9q_82J.css +0 -2
- package/dist/assets/textarea-B9CmN13Z.js +0 -1
- package/dist/assets/tooltip-DSjr26A_.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-C6kAkizT.js +0 -1
- package/dist/assets/useMobileNavigationStore-DDgESrzu.js +0 -413
- package/dist/assets/useSessionAutoCleanup-q2lauc7x.js +0 -2
- package/dist/assets/utils-2ZK8yboQ.js +0 -1
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-Cfru2-qr.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
@@ -572,8 +572,8 @@
|
|
|
572
572
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-CfgOfnZk.js">
|
|
573
573
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
574
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
576
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DXXOBrK_.js">
|
|
576
|
+
<link rel="stylesheet" crossorigin href="/assets/src-C_Yn9Ylz.css">
|
|
577
577
|
</head>
|
|
578
578
|
<body class="h-full bg-background text-foreground">
|
|
579
579
|
<!-- Keep the startup layer outside #root. React replaces every child of
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-BphFgAb_.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-CfgOfnZk.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
98
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DXXOBrK_.js">
|
|
98
|
+
<link rel="stylesheet" crossorigin href="/assets/src-C_Yn9Ylz.css">
|
|
99
99
|
</head>
|
|
100
100
|
<body class="h-full bg-background text-foreground">
|
|
101
101
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-BVRPTuLT.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-CfgOfnZk.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
87
|
-
<link rel="stylesheet" crossorigin href="/assets/src-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DXXOBrK_.js">
|
|
87
|
+
<link rel="stylesheet" crossorigin href="/assets/src-C_Yn9Ylz.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
|
90
90
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -86,6 +86,7 @@ import {
|
|
|
86
86
|
} from './lib/notifications/live-activity-refresh-runtime.js';
|
|
87
87
|
import { createNotificationTemplateRuntime } from './lib/notifications/template-runtime.js';
|
|
88
88
|
import { createPermissionAutoAcceptRuntime } from './lib/permission-auto-accept/runtime.js';
|
|
89
|
+
import { createQuestionAutoDelegateRuntime } from './lib/question-auto-delegate/runtime.js';
|
|
89
90
|
import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
|
|
90
91
|
import { createProjectConfigRuntime } from './lib/projects/project-config.js';
|
|
91
92
|
import { createRemoteClientAuthRuntime } from './lib/client-auth/remote-clients.js';
|
|
@@ -706,10 +707,25 @@ const sessionTitleRuntime = createSessionTitleRuntime({
|
|
|
706
707
|
getSmallModelService,
|
|
707
708
|
});
|
|
708
709
|
|
|
710
|
+
// Forward declaration: question auto-delegate is created after the event hub;
|
|
711
|
+
// goal hooks close over this binding so auto-delegate can keep goals alive.
|
|
712
|
+
let questionAutoDelegateRuntime = null;
|
|
713
|
+
/** Filled inside main() once the SQLite session index is constructed. */
|
|
714
|
+
let sessionIndexServiceRef = null;
|
|
715
|
+
|
|
709
716
|
const sessionGoalRuntime = createSessionGoalRuntime({
|
|
710
717
|
buildOpenCodeUrl,
|
|
711
718
|
getOpenCodeAuthHeaders,
|
|
712
719
|
getSmallModelService,
|
|
720
|
+
shouldKeepGoalActiveForQuestion: (sessionId) => (
|
|
721
|
+
questionAutoDelegateRuntime?.isAutoHandling(sessionId) === true
|
|
722
|
+
),
|
|
723
|
+
isQuestionBlockingGoal: (sessionId) => (
|
|
724
|
+
questionAutoDelegateRuntime?.isBlockingSession(sessionId) === true
|
|
725
|
+
),
|
|
726
|
+
onGoalPaused: (sessionId, directory) => (
|
|
727
|
+
questionAutoDelegateRuntime?.pauseForSessionTree?.(sessionId, directory)
|
|
728
|
+
),
|
|
713
729
|
emitGoalNotification: async ({ sessionId, directory, status, goal }) => {
|
|
714
730
|
// The goal settle notification replaces the per-turn ready notifications
|
|
715
731
|
// (suppressed while the goal is active) — so it obeys the same toggle.
|
|
@@ -765,6 +781,38 @@ notificationTriggerRuntime.setGetIsSessionAutoAccepting(
|
|
|
765
781
|
(sessionId, directory) => permissionAutoAcceptRuntime.isSessionAutoAccepting(sessionId, directory),
|
|
766
782
|
);
|
|
767
783
|
|
|
784
|
+
const broadcastQuestionAutoDelegateTip = createOpenChamberEventBroadcaster({
|
|
785
|
+
getOpenChamberEventClients: () => uiOpenChamberEventClients,
|
|
786
|
+
writeSseEvent,
|
|
787
|
+
});
|
|
788
|
+
questionAutoDelegateRuntime = createQuestionAutoDelegateRuntime({
|
|
789
|
+
globalEventHub: globalMessageStreamHub,
|
|
790
|
+
buildOpenCodeUrl,
|
|
791
|
+
getOpenCodeAuthHeaders,
|
|
792
|
+
readSettingsFromDiskMigrated,
|
|
793
|
+
sanitizeProjects,
|
|
794
|
+
listIndexedDirectories: async () => {
|
|
795
|
+
try {
|
|
796
|
+
const snap = sessionIndexServiceRef?.snapshot?.();
|
|
797
|
+
const directories = Array.isArray(snap?.directories) ? snap.directories : [];
|
|
798
|
+
return directories
|
|
799
|
+
.map((entry) => (typeof entry?.directory === 'string' ? entry.directory.trim() : ''))
|
|
800
|
+
.filter(Boolean);
|
|
801
|
+
} catch {
|
|
802
|
+
return [];
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
broadcastGlobalUiEvent,
|
|
806
|
+
broadcastOpenChamberEvent: broadcastQuestionAutoDelegateTip,
|
|
807
|
+
onUserTakeover: ({ sessionID, directory }) => {
|
|
808
|
+
// User pause / manual claim / feature disable → goal pause on root owner.
|
|
809
|
+
void sessionGoalRuntime.pauseForQuestion?.(sessionID, directory)?.catch((error) => {
|
|
810
|
+
console.warn('[question-auto-delegate] goal pause on takeover failed:', error?.message ?? error);
|
|
811
|
+
});
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
questionAutoDelegateRuntime.start();
|
|
815
|
+
|
|
768
816
|
const openCodeWatcherRuntime = createOpenCodeWatcherRuntime({
|
|
769
817
|
waitForOpenCodePort: (...args) => waitForOpenCodePort(...args),
|
|
770
818
|
buildOpenCodeUrl,
|
|
@@ -1109,6 +1157,7 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
|
|
|
1109
1157
|
openCodeWatcherRuntime,
|
|
1110
1158
|
sessionTitleRuntime,
|
|
1111
1159
|
sessionGoalRuntime,
|
|
1160
|
+
questionAutoDelegateRuntime,
|
|
1112
1161
|
sessionRuntime,
|
|
1113
1162
|
getHealthCheckInterval: () => healthCheckInterval,
|
|
1114
1163
|
clearHealthCheckInterval: (value) => clearInterval(value),
|
|
@@ -1263,6 +1312,7 @@ async function main(options = {}) {
|
|
|
1263
1312
|
dbPath: resolveSessionIndexDbPath({ sessionIndexDbPath }, OPENCHAMBER_DATA_DIR),
|
|
1264
1313
|
getRuntimeConfig: () => getDesktopRuntimeConfig?.() ?? null,
|
|
1265
1314
|
});
|
|
1315
|
+
sessionIndexServiceRef = sessionIndexService;
|
|
1266
1316
|
const transcriptCacheService = createTranscriptCacheService({
|
|
1267
1317
|
dbPath: resolveTranscriptCacheDbPath({ transcriptCacheDbPath: options.transcriptCacheDbPath }),
|
|
1268
1318
|
});
|
|
@@ -1615,6 +1665,7 @@ async function main(options = {}) {
|
|
|
1615
1665
|
broadcastGlobalUiEvent,
|
|
1616
1666
|
writeSseEvent,
|
|
1617
1667
|
permissionAutoAcceptRuntime,
|
|
1668
|
+
questionAutoDelegateRuntime,
|
|
1618
1669
|
messageQueueService,
|
|
1619
1670
|
messageQueueRuntime,
|
|
1620
1671
|
globalMessageStreamHub,
|
|
@@ -192,6 +192,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
192
192
|
- `persistSettings(changes)`
|
|
193
193
|
- `setRunExclusivePersist(fn)`
|
|
194
194
|
- Persistent permission auto-accept policy is stored under `permissionAutoAccept`; execution ownership lives in `lib/permission-auto-accept/`.
|
|
195
|
+
- Question auto-delegate toggle is stored under `questionAutoDelegateEnabled` (default on); execution ownership lives in `lib/question-auto-delegate/` (shared `core.d.ts` for VS Code).
|
|
195
196
|
- One-shot compact-chat defaults migration: when disk marker `compactChatDefaultsMigrationVersion` is missing, rewrite legacy/absent `chatRenderMode`/`activityRenderMode`/`showTurnChangedFiles` to `sorted`/`collapsed`/`true` and persist marker `1` (marker stays on disk; response allowlist still hides it). Marker already `1` preserves user values; `persistSettings` runs the same migration before the first write.
|
|
196
197
|
- `startWebUiServer({ settingsPersistLock })` late-binds that shared runner after module load.
|
|
197
198
|
|
|
@@ -1193,6 +1193,8 @@ export const registerCommonRequestMiddleware = (app, dependencies) => {
|
|
|
1193
1193
|
req.path.startsWith('/api/push') ||
|
|
1194
1194
|
req.path.startsWith('/api/notifications') ||
|
|
1195
1195
|
req.path.startsWith('/api/permission-auto-accept') ||
|
|
1196
|
+
req.path.startsWith('/api/question-auto-delegate') ||
|
|
1197
|
+
req.path.startsWith('/api/question') ||
|
|
1196
1198
|
req.path.startsWith('/api/session-folders') ||
|
|
1197
1199
|
req.path.startsWith('/api/small-model') ||
|
|
1198
1200
|
req.path.startsWith('/api/goals') ||
|
|
@@ -7,6 +7,7 @@ import { registerGitRoutes } from '../git/routes.js';
|
|
|
7
7
|
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
|
|
8
8
|
import { registerSessionFoldersRoutes } from '../session-folders/routes.js';
|
|
9
9
|
import { registerPermissionAutoAcceptRoutes } from '../permission-auto-accept/runtime.js';
|
|
10
|
+
import { registerQuestionAutoDelegateRoutes } from '../question-auto-delegate/routes.js';
|
|
10
11
|
import { registerConfigEntityRoutes } from './config-entity-routes.js';
|
|
11
12
|
import { registerSettingsUtilityRoutes } from './core-routes.js';
|
|
12
13
|
import { registerProjectIconRoutes } from './project-icon-routes.js';
|
|
@@ -123,6 +124,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
123
124
|
getOpenChamberEventClients,
|
|
124
125
|
writeSseEvent,
|
|
125
126
|
permissionAutoAcceptRuntime,
|
|
127
|
+
questionAutoDelegateRuntime,
|
|
126
128
|
messageQueueService,
|
|
127
129
|
messageQueueRuntime,
|
|
128
130
|
broadcastGlobalUiEvent,
|
|
@@ -140,6 +142,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
140
142
|
});
|
|
141
143
|
|
|
142
144
|
registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
|
|
145
|
+
// Question auto-delegate + precise /api/question/:id/reply|reject intercepts
|
|
146
|
+
// must win before the generic OpenCode proxy.
|
|
147
|
+
if (questionAutoDelegateRuntime) {
|
|
148
|
+
registerQuestionAutoDelegateRoutes(app, questionAutoDelegateRuntime);
|
|
149
|
+
}
|
|
143
150
|
|
|
144
151
|
registerOpenCodeRoutes(app, {
|
|
145
152
|
crypto,
|
|
@@ -157,6 +164,19 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
157
164
|
refreshOpenCodeAfterConfigChange,
|
|
158
165
|
buildOpenCodeUrl,
|
|
159
166
|
getOpenCodeAuthHeaders,
|
|
167
|
+
onSettingsPersisted: (updated, changes) => {
|
|
168
|
+
if (!questionAutoDelegateRuntime) return;
|
|
169
|
+
if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
|
|
170
|
+
// Only a real boolean may change runtime state — invalid/coerced junk is ignored.
|
|
171
|
+
const fromChanges = changes.questionAutoDelegateEnabled;
|
|
172
|
+
const fromUpdated = updated?.questionAutoDelegateEnabled;
|
|
173
|
+
const next = typeof fromUpdated === 'boolean'
|
|
174
|
+
? fromUpdated
|
|
175
|
+
: (typeof fromChanges === 'boolean' ? fromChanges : null);
|
|
176
|
+
if (typeof next !== 'boolean') return;
|
|
177
|
+
// Persisted successfully — apply immediately. Failed saves never reach here.
|
|
178
|
+
questionAutoDelegateRuntime.applyEnabled(next);
|
|
179
|
+
},
|
|
160
180
|
});
|
|
161
181
|
|
|
162
182
|
registerProjectIconRoutes(app, {
|
|
@@ -43,6 +43,16 @@ describe('feature routes runtime composition', () => {
|
|
|
43
43
|
expect(source).toContain('registerMessageQueueRoutes(app, { messageQueueService, messageQueueRuntime });');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it('registers question auto-delegate routes (including reply/reject intercepts) before proxy composition', async () => {
|
|
47
|
+
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
48
|
+
expect(source).toContain("import { registerQuestionAutoDelegateRoutes } from '../question-auto-delegate/routes.js';");
|
|
49
|
+
expect(source).toMatch(/const \{[\s\S]*questionAutoDelegateRuntime,[\s\S]*\} = routeDependencies;/);
|
|
50
|
+
expect(source).toContain('registerQuestionAutoDelegateRoutes(app, questionAutoDelegateRuntime)');
|
|
51
|
+
const routesSource = await fs.readFile(new URL('../question-auto-delegate/routes.js', import.meta.url), 'utf8');
|
|
52
|
+
expect(routesSource).toContain("app.post('/api/question/:requestID/reply'");
|
|
53
|
+
expect(routesSource).toContain("app.post('/api/question/:requestID/reject'");
|
|
54
|
+
});
|
|
55
|
+
|
|
46
56
|
it('registers session turn-page routes before proxy composition with OpenCode URL/auth deps', async () => {
|
|
47
57
|
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
48
58
|
expect(source).toContain("import { registerSessionTurnPageRoutes } from '../session-turn-pages/routes.js';");
|
|
@@ -21,6 +21,7 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
21
21
|
refreshOpenCodeAfterConfigChange,
|
|
22
22
|
buildOpenCodeUrl,
|
|
23
23
|
getOpenCodeAuthHeaders,
|
|
24
|
+
onSettingsPersisted,
|
|
24
25
|
} = dependencies;
|
|
25
26
|
|
|
26
27
|
let authLibrary = null;
|
|
@@ -328,7 +329,19 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
|
|
|
328
329
|
|
|
329
330
|
app.put('/api/config/settings', async (req, res) => {
|
|
330
331
|
try {
|
|
331
|
-
const
|
|
332
|
+
const changes = req.body ?? {};
|
|
333
|
+
const updated = await persistSettings(changes);
|
|
334
|
+
// Only after a successful persist — failed saves must not apply runtime flags.
|
|
335
|
+
if (
|
|
336
|
+
typeof onSettingsPersisted === 'function'
|
|
337
|
+
&& Object.prototype.hasOwnProperty.call(changes, 'questionAutoDelegateEnabled')
|
|
338
|
+
) {
|
|
339
|
+
try {
|
|
340
|
+
onSettingsPersisted(updated, changes);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.warn('[API:PUT /api/config/settings] onSettingsPersisted failed:', error?.message ?? error);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
332
345
|
res.json(updated);
|
|
333
346
|
} catch (error) {
|
|
334
347
|
console.error('[API:PUT /api/config/settings] Failed to save settings:', error);
|
|
@@ -324,6 +324,9 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
324
324
|
if (typeof candidate.sessionGoalEnabled === 'boolean') {
|
|
325
325
|
result.sessionGoalEnabled = candidate.sessionGoalEnabled;
|
|
326
326
|
}
|
|
327
|
+
if (typeof candidate.questionAutoDelegateEnabled === 'boolean') {
|
|
328
|
+
result.questionAutoDelegateEnabled = candidate.questionAutoDelegateEnabled;
|
|
329
|
+
}
|
|
327
330
|
if (typeof candidate.sessionGoalDefaultBudgetEnabled === 'boolean') {
|
|
328
331
|
result.sessionGoalDefaultBudgetEnabled = candidate.sessionGoalDefaultBudgetEnabled;
|
|
329
332
|
}
|
|
@@ -135,6 +135,18 @@ describe('settings helpers', () => {
|
|
|
135
135
|
});
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
it('accepts questionAutoDelegateEnabled as a persisted boolean setting', () => {
|
|
139
|
+
const helpers = createTestHelpers();
|
|
140
|
+
|
|
141
|
+
expect(helpers.sanitizeSettingsUpdate({ questionAutoDelegateEnabled: true })).toEqual({
|
|
142
|
+
questionAutoDelegateEnabled: true,
|
|
143
|
+
});
|
|
144
|
+
expect(helpers.sanitizeSettingsUpdate({ questionAutoDelegateEnabled: false })).toEqual({
|
|
145
|
+
questionAutoDelegateEnabled: false,
|
|
146
|
+
});
|
|
147
|
+
expect(helpers.sanitizeSettingsUpdate({ questionAutoDelegateEnabled: 'true' })).toEqual({});
|
|
148
|
+
});
|
|
149
|
+
|
|
138
150
|
it('accepts desktopUiPassword as a persisted shared setting', () => {
|
|
139
151
|
const helpers = createTestHelpers();
|
|
140
152
|
|
|
@@ -10,6 +10,7 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
10
10
|
sessionRuntime,
|
|
11
11
|
sessionTitleRuntime,
|
|
12
12
|
sessionGoalRuntime,
|
|
13
|
+
questionAutoDelegateRuntime,
|
|
13
14
|
scheduledTasksRuntime,
|
|
14
15
|
getHealthCheckInterval,
|
|
15
16
|
clearHealthCheckInterval,
|
|
@@ -44,6 +45,7 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
44
45
|
sessionRuntime.dispose();
|
|
45
46
|
sessionTitleRuntime?.stop?.();
|
|
46
47
|
sessionGoalRuntime?.stop?.();
|
|
48
|
+
questionAutoDelegateRuntime?.dispose?.();
|
|
47
49
|
// Stop timers/queue only. Keep the process-lifetime run history store open so
|
|
48
50
|
// in-flight attach/finalize can still write, and so exitProcess:false restarts
|
|
49
51
|
// can reuse the same global singleton without reopening a closed SQLite handle.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Question Auto-Delegate
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Server-owned automatic handling of OpenCode `question.asked` prompts. When
|
|
6
|
+
enabled (default), pending questions are answered after 30 seconds with a fixed
|
|
7
|
+
continuation text so agents (including background/subagent sessions) can proceed
|
|
8
|
+
without a connected UI. Manual reply/reject still use the official OpenCode
|
|
9
|
+
SDK shape; the Host only adds a single-flight claim so timer, manual, and
|
|
10
|
+
delegate paths cannot double-POST upstream.
|
|
11
|
+
|
|
12
|
+
Permission auto-accept is intentionally out of scope — this module never
|
|
13
|
+
replies to permission requests.
|
|
14
|
+
|
|
15
|
+
## Settings
|
|
16
|
+
|
|
17
|
+
Persisted via existing `PUT /api/config/settings`:
|
|
18
|
+
|
|
19
|
+
| Field | Type | Default |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `questionAutoDelegateEnabled` | `boolean` | `true` (absent ⇒ on) |
|
|
22
|
+
|
|
23
|
+
After a **successful** persist that includes a **real boolean** value, the Host
|
|
24
|
+
calls `applyEnabled`. Invalid/non-boolean input must not change runtime state.
|
|
25
|
+
Failed saves never call `applyEnabled`.
|
|
26
|
+
|
|
27
|
+
Boot/`start()` loads settings asynchronously with a **generation guard**: a
|
|
28
|
+
concurrent `applyEnabled` wins over a late disk read. Settings read failure
|
|
29
|
+
enters `unavailable` (`enabled=false`, `coverage.partial`) and **never** falls
|
|
30
|
+
back to auto-answering as if enabled.
|
|
31
|
+
|
|
32
|
+
- `false`: cancel timers; pending → `disabled`; notify goal takeover pause.
|
|
33
|
+
- `false → true`: still-pending requests restart a fresh 30s window (directory-bound only).
|
|
34
|
+
- repeated `true`: no-op (does not reset deadlines).
|
|
35
|
+
- Feature **off** still allows an explicit single-shot `delegate`.
|
|
36
|
+
|
|
37
|
+
Auto-submit requires **settings ready + enabled + authoritative directory**.
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
| File | Role |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `core.js` / `core.d.ts` | Platform-agnostic state machine (IO/timer injected). Shared contract for VS Code. |
|
|
44
|
+
| `runtime.js` | Web adapter: OpenCode upstream fetch, global event hub, settings, SSE tips, project + session-index directories. |
|
|
45
|
+
| `routes.js` | OpenChamber routes + precise `/api/question/:id/reply\|reject` intercepts. |
|
|
46
|
+
|
|
47
|
+
## Snapshot
|
|
48
|
+
|
|
49
|
+
`GET /api/question-auto-delegate` returns the snapshot in `core.d.ts`.
|
|
50
|
+
|
|
51
|
+
Tip event (not a data payload):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
{ type: 'openchamber:question-auto-delegate-changed', properties: { epoch, revision } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## HTTP API
|
|
58
|
+
|
|
59
|
+
| Method | Path | Notes |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `GET` | `/api/question-auto-delegate` | snapshot |
|
|
62
|
+
| `POST` | `.../requests/:id/pause` | `{ sessionID, directory, reason: 'interaction'\|'user' }` — bound identity conflicts → 409 |
|
|
63
|
+
| `POST` | `.../requests/:id/delegate` | recovers identity via scoped list; allowed when toggle off |
|
|
64
|
+
| `POST` | `/api/question/:id/reply` | manual: `answers` must be an array (incl. `[]`); never auto text |
|
|
65
|
+
| `POST` | `/api/question/:id/reject` | SDK body preserved |
|
|
66
|
+
|
|
67
|
+
Concurrent claim losers: HTTP **409** + `code: "question_submission_claimed"`.
|
|
68
|
+
|
|
69
|
+
## Claim / upstream rules
|
|
70
|
+
|
|
71
|
+
1. One authority owns at most one upstream POST per `requestID` (sync claim).
|
|
72
|
+
2. **Definitive reject** (400/409/422) or pre-send throw → **release claim**, pause for human retry. **No immediate auto retry.**
|
|
73
|
+
3. **Unclear post-dispatch result** (timeout/5xx/`uncertain`) → `uncertain` tombstone; no retry; still **blocks** goal.
|
|
74
|
+
4. **Settled** must not be downgraded by a late POST failure.
|
|
75
|
+
5. Successful **scoped** empty list may converge `uncertain`/pending → `external` settled **only for that directory**.
|
|
76
|
+
6. Manual reply forwards `answers` as-is (including `[]`). Fixed text is auto/delegate only.
|
|
77
|
+
7. Client `sessionID`/`directory` hints never override a bound identity (conflict → 409).
|
|
78
|
+
8. Auto-submit requires an authoritative directory binding (from event, session cache, or `getSession` write-back). Missing directory → no timer.
|
|
79
|
+
9. Unknown pause/delegate recovers via scoped `question.list` — never invents `questionCount=1`.
|
|
80
|
+
|
|
81
|
+
## Reconcile
|
|
82
|
+
|
|
83
|
+
- Inventory = projects + session-index directories + observed request/event dirs.
|
|
84
|
+
- Track `successfulDirectories`; only those may settle missing requests.
|
|
85
|
+
- Unscoped list discovers unknowns only; never means “all directories empty”.
|
|
86
|
+
- Unscoped / directory-less pending triggers `ensureSessionLineage(sessionID, hint, requestID)`
|
|
87
|
+
so `getSession` can write the real worktree directory back and arm the 30s timer.
|
|
88
|
+
- Single-flight with trailing scope merge.
|
|
89
|
+
- Inventory/list failure → `coverage.partial`; prior state retained.
|
|
90
|
+
|
|
91
|
+
## UpstreamResult (`core.d.ts`)
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
{ ok, uncertain?, notSent?, status, body }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `notSent: true` (compatible extension): request never left the process → release claim, pause for retry.
|
|
98
|
+
- Bare `status: 0` without `notSent` → `uncertain` (may have been sent). VS Code/web adapters that know the POST was never transmitted must set `notSent: true`.
|
|
99
|
+
|
|
100
|
+
## Session goal linkage
|
|
101
|
+
|
|
102
|
+
- Auto-handling ask: clear idle timer only (goal stays `active`).
|
|
103
|
+
- `isBlockingSession` includes `uncertain` (and counting/paused/disabled/submitting).
|
|
104
|
+
- Manual claim / user pause / disable → `onUserTakeover` → goal `pauseForQuestion` (walks to **root owner**, uses owner directory).
|
|
105
|
+
- Goal abort/pause → `onGoalPaused` → `pauseForSessionTree` (pause related question timers, `pauseReason: 'goal'`).
|
|
106
|
+
- Continuation re-checks blocker immediately before `prompt_async`.
|
|
107
|
+
|
|
108
|
+
## VS Code
|
|
109
|
+
|
|
110
|
+
`core.d.ts` is the shared contract. Compatible extensions in this fix:
|
|
111
|
+
|
|
112
|
+
- `readEnabled()` may return `null` (unavailable).
|
|
113
|
+
- `pauseForSessionTree` / `resolveRootSession` added.
|
|
114
|
+
- `applyEnabled` ignores non-boolean.
|
|
115
|
+
- Snapshot field set unchanged.
|
|
116
|
+
|
|
117
|
+
## Tests
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
bunx vitest run --project @openchamber/web \
|
|
121
|
+
packages/web/server/lib/question-auto-delegate/core.test.js \
|
|
122
|
+
packages/web/server/lib/question-auto-delegate/routes.test.js \
|
|
123
|
+
packages/web/server/lib/session-goal/runtime.test.js
|
|
124
|
+
```
|