@wrongstack/webui-server 0.313.1 → 0.316.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/index.js +4717 -1291
  2. package/dist/server/agent-roster-handlers.d.ts +3 -2
  3. package/dist/server/backend-services.d.ts +33 -1
  4. package/dist/server/boot.d.ts +2 -1
  5. package/dist/server/brain-handlers.d.ts +21 -4
  6. package/dist/server/chimera-routes.d.ts +88 -0
  7. package/dist/server/codebase-index-server-control.d.ts +2 -1
  8. package/dist/server/codebase-indexing.d.ts +3 -2
  9. package/dist/server/codemap-cache.d.ts +0 -5
  10. package/dist/server/codemap-handlers.d.ts +2 -1
  11. package/dist/server/collab/controller.d.ts +0 -2
  12. package/dist/server/collab/session-registry.d.ts +2 -1
  13. package/dist/server/connection-handler.d.ts +2 -1
  14. package/dist/server/connections/auto-healer.d.ts +5 -6
  15. package/dist/server/connections/collector.d.ts +0 -3
  16. package/dist/server/connections/helpers.d.ts +0 -2
  17. package/dist/server/connections/service-actions.d.ts +0 -4
  18. package/dist/server/content-routes.d.ts +6 -1
  19. package/dist/server/conversation-operations.d.ts +18 -2
  20. package/dist/server/deadcode-handlers.d.ts +2 -1
  21. package/dist/server/design-handlers.d.ts +6 -0
  22. package/dist/server/discover-mailbox-bridge.d.ts +2 -1
  23. package/dist/server/embedded-host-adapters.d.ts +120 -2
  24. package/dist/server/embedded-message-router.d.ts +15 -1
  25. package/dist/server/entry.js +4301 -1075
  26. package/dist/server/file-handlers.d.ts +5 -2
  27. package/dist/server/git-handlers.d.ts +19 -0
  28. package/dist/server/governance-runtime.d.ts +1 -1
  29. package/dist/server/handlers.js +6 -9
  30. package/dist/server/http-server/analytics-handler.d.ts +0 -2
  31. package/dist/server/http-server/security-helpers.d.ts +0 -6
  32. package/dist/server/http-server/vector-memory-handlers.d.ts +2 -56
  33. package/dist/server/index.d.ts +12 -5
  34. package/dist/server/introspection-routes.d.ts +16 -0
  35. package/dist/server/kanban-contract-routes.d.ts +19 -1
  36. package/dist/server/kanban-decomposition-routes.d.ts +4 -1
  37. package/dist/server/kanban-dispatch.d.ts +2 -0
  38. package/dist/server/kanban-route-helpers.d.ts +21 -0
  39. package/dist/server/kanban-routes.d.ts +13 -1
  40. package/dist/server/kanban-supervisor.d.ts +2 -0
  41. package/dist/server/kanban-task-routes.d.ts +4 -1
  42. package/dist/server/lifecycle.d.ts +2 -1
  43. package/dist/server/mcp-handlers.d.ts +2 -1
  44. package/dist/server/message-dispatcher.d.ts +19 -5
  45. package/dist/server/mode-handlers.d.ts +6 -4
  46. package/dist/server/mode-operations.d.ts +15 -4
  47. package/dist/server/model-catalog-search.d.ts +2 -1
  48. package/dist/server/model-operations.d.ts +6 -2
  49. package/dist/server/pending-confirms.d.ts +27 -1
  50. package/dist/server/port-utils.d.ts +16 -2
  51. package/dist/server/pref-helpers.d.ts +1 -1
  52. package/dist/server/prefs-handlers.d.ts +43 -6
  53. package/dist/server/prefs-routes.d.ts +11 -2
  54. package/dist/server/process-handlers.d.ts +12 -5
  55. package/dist/server/project-handlers.d.ts +12 -0
  56. package/dist/server/project-watcher.d.ts +19 -10
  57. package/dist/server/provider/mutations.d.ts +0 -1
  58. package/dist/server/provider-fanout.d.ts +46 -0
  59. package/dist/server/provider-handlers.d.ts +6 -2
  60. package/dist/server/provider-routes.d.ts +3 -0
  61. package/dist/server/provider-store.d.ts +2 -1
  62. package/dist/server/requirement-intake-handlers.d.ts +0 -2
  63. package/dist/server/route-family-dispatcher.d.ts +2 -0
  64. package/dist/server/routes.d.ts +41 -5
  65. package/dist/server/sdd-board-ws-handler.d.ts +2 -2
  66. package/dist/server/server-runtime.d.ts +23 -6
  67. package/dist/server/session-agent-registry.d.ts +145 -0
  68. package/dist/server/session-agent-sessions.d.ts +49 -0
  69. package/dist/server/session-cleanup-scheduler.d.ts +3 -2
  70. package/dist/server/session-deletion.d.ts +1 -0
  71. package/dist/server/session-handlers.d.ts +82 -1
  72. package/dist/server/session-history.d.ts +4 -3
  73. package/dist/server/session-routes.d.ts +2 -0
  74. package/dist/server/session-scoped-prefs.d.ts +28 -0
  75. package/dist/server/setup-events-core-watchers.d.ts +7 -2
  76. package/dist/server/setup-events-fleet-broadcaster.d.ts +7 -2
  77. package/dist/server/setup-events-pattern-handlers.d.ts +5 -1
  78. package/dist/server/setup-events-provider-handlers.d.ts +1 -1
  79. package/dist/server/setup-events-session-helpers.d.ts +14 -1
  80. package/dist/server/setup-events-status-watcher.d.ts +7 -2
  81. package/dist/server/setup-events-subagent-handlers.d.ts +5 -1
  82. package/dist/server/setup-events-tool-handlers.d.ts +14 -1
  83. package/dist/server/setup-events.d.ts +21 -1
  84. package/dist/server/setup-screen.d.ts +3 -2
  85. package/dist/server/shell-git-routes.d.ts +4 -0
  86. package/dist/server/standalone-session-identity.d.ts +3 -3
  87. package/dist/server/start-webui-proxy-apply.d.ts +2 -1
  88. package/dist/server/start-webui-shutdown.d.ts +15 -0
  89. package/dist/server/system-prompt-handlers.d.ts +67 -0
  90. package/dist/server/system-prompt-rebuild.d.ts +45 -0
  91. package/dist/server/terminal-dashboard.d.ts +145 -0
  92. package/dist/server/types.d.ts +33 -4
  93. package/dist/server/webui-status-logger.d.ts +61 -0
  94. package/dist/server/worklist-routes.d.ts +8 -2
  95. package/dist/server/worklist-session-context.d.ts +44 -0
  96. package/dist/server/worktree-ws-handler.d.ts +2 -1
  97. package/dist/server/ws-payload-validation.d.ts +27 -2
  98. package/dist/server/ws-utils.d.ts +29 -4
  99. package/package.json +16 -15
@@ -19,6 +19,32 @@ export interface PendingConfirm {
19
19
  payload?: Record<string, unknown> | undefined;
20
20
  }
21
21
  export declare function isDestructivePendingConfirm(confirm: PendingConfirm): boolean;
22
- export declare function resolveYoloEligiblePendingConfirms(pendingConfirms: Map<string, PendingConfirm>): void;
22
+ /**
23
+ * Answer "yes" to the prompts YOLO now covers — for ONE session.
24
+ *
25
+ * `pendingConfirms` is one process-wide map holding the prompts of every open
26
+ * tab. YOLO is a per-tab preference, so sweeping the whole map meant turning
27
+ * it on in one tab silently approved the tool waiting on a prompt in another —
28
+ * a conversation the user was not even looking at. Passing no session keeps
29
+ * the old blanket behaviour for single-session hosts.
30
+ */
31
+ export declare function resolveYoloEligiblePendingConfirms(pendingConfirms: Map<string, PendingConfirm>, sessionId?: string | undefined): void;
23
32
  export declare function resolveAllPendingConfirms(pendingConfirms: Map<string, PendingConfirm>, decision: ConfirmDecision): void;
33
+ /**
34
+ * Answer the prompts of a session that no surface displays any more.
35
+ *
36
+ * A permission prompt raised in a background tab is PARKED on that tab's lane
37
+ * (`chat.setPendingConfirm`). Closing the tab disposes the lane, so the parked
38
+ * prompt is gone from the client while the server's resolver stays pending
39
+ * forever — `agent.run` never settles, its run lock is never released, and the
40
+ * session becomes an unkillable ghost: `session.delete` refuses it with "an
41
+ * agent run is active" and `retireUndisplayedSessions` skips it. The blanket
42
+ * `resolveAllPendingConfirms` drain only fires when the LAST socket goes away,
43
+ * which never happens while the other three tabs are open.
44
+ *
45
+ * `no` (not `deny`) on purpose: the tool fails this once and the model can
46
+ * react, without minting a session-scoped denial for a tool+pattern the user
47
+ * never actually refused.
48
+ */
49
+ export declare function resolvePendingConfirmsForSession(pendingConfirms: Map<string, PendingConfirm>, sessionId: string, decision?: ConfirmDecision): number;
24
50
  //# sourceMappingURL=pending-confirms.d.ts.map
@@ -52,7 +52,7 @@ export declare function getSurfaceDefaultPorts(surface: SurfaceKind): {
52
52
  };
53
53
  /** Resolve true when `port` can be bound on `host`, false on EADDRINUSE/EACCES. */
54
54
  export declare function isPortFree(host: string, port: number): Promise<boolean>;
55
- export interface FindFreePortOptions {
55
+ interface FindFreePortOptions {
56
56
  /** Ports to skip even if free (e.g. one already chosen for the sibling server). */
57
57
  exclude?: Set<number> | undefined;
58
58
  /** How many consecutive ports to try before giving up. Default 200. */
@@ -63,9 +63,17 @@ export interface FindFreePortOptions {
63
63
  * `exclude`. Throws if nothing is free within `maxTries` steps.
64
64
  */
65
65
  export declare function findFreePort(host: string, startPort: number, opts?: FindFreePortOptions): Promise<number>;
66
- export interface ListenWithRetryOptions {
66
+ interface ListenWithRetryOptions {
67
67
  /** Bind attempts before giving up on EADDRINUSE. Default 10. */
68
68
  maxTries?: number | undefined;
69
+ /**
70
+ * Same-port re-binds attempted before advancing, when the pre-bind probe
71
+ * said the port was free and the real bind still reported EADDRINUSE.
72
+ * Default 2 (see the phantom-EADDRINUSE note in `listenWithRetry`).
73
+ */
74
+ phantomRetries?: number | undefined;
75
+ /** Delay between those same-port re-binds, ms. Default 30. */
76
+ phantomRetryDelayMs?: number | undefined;
69
77
  }
70
78
  /**
71
79
  * Listen `server` on `host` starting at `port`, advancing one port on each
@@ -74,6 +82,12 @@ export interface ListenWithRetryOptions {
74
82
  * error (EACCES, EAFNOSUPPORT, …) rejects immediately; EADDRINUSE past the
75
83
  * final attempt rejects with the last error. Resolves with the port the
76
84
  * server actually bound, which exceeds the request when a retry advanced.
85
+ *
86
+ * Two hazards this must survive, both observed under Bun on Windows:
87
+ * - a co-listener that throws from the server's 'error' emit (see the
88
+ * prependOnceListener note below) — settlement must not depend on it;
89
+ * - a phantom EADDRINUSE on a port the pre-bind probe just released.
77
90
  */
78
91
  export declare function listenWithRetry(server: net.Server, host: string, port: number, opts?: ListenWithRetryOptions): Promise<number>;
92
+ export {};
79
93
  //# sourceMappingURL=port-utils.d.ts.map
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import type { SecretVault } from '@wrongstack/core/types';
19
19
  /** Pref keys exposed to the settings panel via prefs.get / prefs.updated. */
20
- export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'nextStepsTool', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewModelSelection', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'autoCollapseInput', 'pluginsEnabled', 'fleetChatVerbosity', 'wrongProxyEnabled', 'wrongProxyUrl'];
20
+ export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'nextStepsTool', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'systemPromptVariant', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'modelTiers', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewModelSelection', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'autoCollapseInput', 'pluginsEnabled', 'fleetChatVerbosity', 'wrongProxyEnabled', 'wrongProxyUrl', 'showAgentSwarmPanel', 'readSymbols', 'showSageMemoryInject', 'sageMemoryInjectThreshold', 'preRefineSeconds', 'multiDiffSummaryThreshold', 'enhanceCountdownMs', 'keyboardShortcuts'];
21
21
  export interface PrefHelperDeps {
22
22
  /** Path to the active profile config; the sole settings mutation target. */
23
23
  profileConfigPath: string;
@@ -1,15 +1,44 @@
1
1
  import type { ConfigStore } from '@wrongstack/core/types';
2
2
  import type { WebSocket } from 'ws';
3
3
  import { type PendingConfirm } from './pending-confirms.js';
4
+ import { type SystemPromptSurface } from './system-prompt-handlers.js';
4
5
  import type { WSServerMessage } from './types.js';
6
+ export { SESSION_SCOPED_PREF_KEYS } from './session-scoped-prefs.js';
5
7
  export interface PrefsHandlerContext {
6
8
  meta: Record<string, unknown>;
7
- snapshot: () => Record<string, unknown>;
9
+ /**
10
+ * Meta bag of a specific session, for the session-scoped keys above.
11
+ * Falls back to the process-wide `meta` when the host has not wired
12
+ * per-session contexts (the embedded single-session runtimes).
13
+ */
14
+ metaFor?: ((sessionId?: string) => Record<string, unknown>) | undefined;
15
+ /**
16
+ * Read the pref snapshot for ONE session. The session-scoped keys live on
17
+ * that session's meta, so a snapshot taken without an id describes whichever
18
+ * session the runtime is on — which is a different tab from the asking one
19
+ * as often as not once four are open.
20
+ */
21
+ snapshot: (sessionId?: string) => Record<string, unknown>;
8
22
  persist: (payload: Record<string, unknown>) => Promise<void>;
9
23
  pendingConfirms: Map<string, PendingConfirm>;
10
24
  configStore?: ConfigStore | undefined;
11
- setYolo?: ((enabled: boolean) => void) | undefined;
12
- setAutonomy?: ((mode: string) => void) | undefined;
25
+ /**
26
+ * Flip the host's process-wide YOLO fallback.
27
+ *
28
+ * `sessionId` names the tab that asked. A host whose "apply" path also
29
+ * writes a context meta must NOT write the leader's when a tab is named —
30
+ * the leader context is the boot tab's runtime, so that write would turn
31
+ * YOLO on for a conversation the user was not in.
32
+ */
33
+ setYolo?: ((enabled: boolean, sessionId?: string | undefined) => void) | undefined;
34
+ /**
35
+ * Flip the RUNTIME autonomy mode. Session-scoped for the same reason
36
+ * `setYolo` is: the mode is a per-tab preference, and the runtime knob
37
+ * behind this seam is process-wide, so an unaddressed call let a background
38
+ * tab put the whole process — and every other tab's system prompt — into
39
+ * eternal mode.
40
+ */
41
+ setAutonomy?: ((mode: string, sessionId?: string | undefined) => void) | undefined;
13
42
  applyConfigPrefs?: ((payload: Record<string, unknown>) => void) | undefined;
14
43
  setAutoCompact?: ((enabled: boolean) => void) | undefined;
15
44
  setLogLevel?: ((level: 'debug' | 'info' | 'warn' | 'error') => void) | undefined;
@@ -23,10 +52,18 @@ export interface PrefsHandlerContext {
23
52
  * prefs-dependent request (e.g. an immediate model.switch).
24
53
  */
25
54
  applyWrongProxyPrefs?: ((payload: Record<string, unknown>) => void | Promise<void>) | undefined;
55
+ /**
56
+ * Identity-prompt picker. Optional so a host that has not wired it still
57
+ * compiles; `system_prompt.get` then answers with an explicit "unavailable"
58
+ * payload rather than leaving the browser's request unanswered.
59
+ */
60
+ systemPrompt?: SystemPromptSurface | undefined;
26
61
  send: (ws: WebSocket, message: WSServerMessage) => void;
27
62
  broadcast: (message: WSServerMessage) => void;
28
63
  }
29
- export declare function handlePrefsGet(ctx: PrefsHandlerContext, ws: WebSocket): void;
30
- export declare function handlePrefsUpdate(ctx: PrefsHandlerContext, ws: WebSocket, input: Record<string, unknown>): Promise<void>;
31
- export declare function handleAutonomySwitch(ctx: PrefsHandlerContext, ws: WebSocket, mode: string): void;
64
+ export declare function handlePrefsGet(ctx: PrefsHandlerContext, ws: WebSocket, sessionId?: string): void;
65
+ /** Answer `system_prompt.get` with the variant catalogue and token estimates. */
66
+ export declare function handleSystemPromptGet(ctx: PrefsHandlerContext, ws: WebSocket, sessionId?: string): Promise<void>;
67
+ export declare function handlePrefsUpdate(ctx: PrefsHandlerContext, ws: WebSocket, input: Record<string, unknown>, sessionId?: string): Promise<void>;
68
+ export declare function handleAutonomySwitch(ctx: PrefsHandlerContext, ws: WebSocket, mode: string, sessionId?: string): void;
32
69
  //# sourceMappingURL=prefs-handlers.d.ts.map
@@ -2,8 +2,8 @@ import type { WebSocket } from 'ws';
2
2
  import { type PrefsHandlerContext } from './prefs-handlers.js';
3
3
  import type { WSClientMessage } from './types.js';
4
4
  export interface PrefsRouteHandlers {
5
- /** Respond to the WS client with the current pref snapshot. */
6
- getPrefs: (ws: WebSocket) => Promise<void>;
5
+ /** Respond to the WS client with one session's pref snapshot. */
6
+ getPrefs: (ws: WebSocket, sessionId?: string) => Promise<void>;
7
7
  /**
8
8
  * Merge the supplied pref payload into context.meta, persist the durable
9
9
  * keys to config.json, apply any runtime effects (YOLO toggle, feature-flag
@@ -11,6 +11,14 @@ export interface PrefsRouteHandlers {
11
11
  * logger.level), then broadcast the full current snapshot to all clients.
12
12
  */
13
13
  updatePrefs: (ws: WebSocket, payload: Record<string, unknown>) => Promise<void>;
14
+ /**
15
+ * Reply with the identity-prompt variant catalogue: label, hint and an
16
+ * upper-bound token estimate per variant, plus which one is live and whether
17
+ * the user has ever chosen. Setting a variant goes through `prefs.update`
18
+ * with `systemPromptVariant`, so this side stays read-only.
19
+ */
20
+ /** `sessionId` names the tab asking: the live variant is per session. */
21
+ getSystemPrompt: (ws: WebSocket, sessionId?: string | undefined) => Promise<void>;
14
22
  /** Preview/apply canonical defaults in the active profile config. */
15
23
  doctorConfig?: ((ws: WebSocket, apply: boolean) => Promise<void>) | undefined;
16
24
  }
@@ -24,6 +32,7 @@ export declare function createPrefsRouteHandlers(ctx: PrefsHandlerContext, docto
24
32
  * Owned prefixes:
25
33
  * - `prefs.get`
26
34
  * - `prefs.update`
35
+ * - `system_prompt.get`
27
36
  * - `config.doctor`
28
37
  *
29
38
  * Regression-tested by packages/webui/tests/server/dispatcher-routing.test.ts.
@@ -2,21 +2,28 @@
2
2
  * Process-registry WebSocket handlers for the WebUI server, extracted from the
3
3
  * `handleMessage` switch in `index.ts` as part of splitting that file (#31).
4
4
  *
5
- * case 'process.list': return handleProcessList(ws);
5
+ * case 'process.list': return handleProcessList(ws, msg);
6
6
  * case 'process.kill': return handleProcessKill(ws, msg.payload);
7
- * case 'process.killAll': return handleProcessKillAll(ws);
7
+ * case 'process.killAll': return handleProcessKillAll(ws, …, msg.payload);
8
+ *
9
+ * Four tabs share one registry. A list/kill that ignores sessionId lets tab 2
10
+ * see (and terminate) tab 1's bash. When the request names a session, list
11
+ * and kill-all are confined to that session's children; a kill of a PID that
12
+ * belongs to a different session is refused.
8
13
  *
9
14
  * Registry failures never escape the handler; they are reported over the
10
15
  * socket using the existing protocol envelopes.
11
16
  */
12
- import type { Logger } from '@wrongstack/core/types';
13
17
  import { type TrustBoundary } from '@wrongstack/core/security';
18
+ import type { Logger } from '@wrongstack/core/types';
14
19
  import type { WebSocket } from 'ws';
15
20
  import { type WebUIPrivilegedAction } from './privileged-actions.js';
16
21
  /** Broadcast the tracked-process list; an empty list on any registry failure. */
17
- export declare function handleProcessList(ws: WebSocket): void;
22
+ export declare function handleProcessList(ws: WebSocket, msg?: {
23
+ payload?: unknown;
24
+ }): void;
18
25
  /** Kill one tracked PID. Rejects invalid payloads and protected processes. */
19
26
  export declare function handleProcessKill(ws: WebSocket, payload: unknown, trustBoundary?: TrustBoundary, logger?: Logger | undefined, metadata?: WebUIPrivilegedAction['metadata'] | undefined): Promise<void>;
20
27
  /** Kill every tracked process. */
21
- export declare function handleProcessKillAll(ws: WebSocket, trustBoundary?: TrustBoundary, logger?: Logger | undefined, metadata?: WebUIPrivilegedAction['metadata'] | undefined): Promise<void>;
28
+ export declare function handleProcessKillAll(ws: WebSocket, trustBoundary?: TrustBoundary, logger?: Logger | undefined, metadata?: WebUIPrivilegedAction['metadata'] | undefined, payload?: unknown): Promise<void>;
22
29
  //# sourceMappingURL=process-handlers.d.ts.map
@@ -41,6 +41,18 @@ export interface ProjectHandlersContext {
41
41
  onSessionSwapped?: (sessionId: string, target?: SessionIdentityTarget) => void | Promise<void>;
42
42
  allowProjectMutations?: boolean;
43
43
  sessionStartPayload: (overrides?: Record<string, unknown>) => Promise<unknown>;
44
+ /**
45
+ * Deliver to EVERY connection, bypassing the session filter.
46
+ *
47
+ * Both hosts now route a broadcast by the `sessionId` on its payload, which
48
+ * is right for everything that describes one tab and wrong for the project
49
+ * switch: it announces a session created a millisecond ago, so no client can
50
+ * be subscribed to it and the filter dropped the switch on the floor —
51
+ * exactly the "changed NOTHING in the browser" failure this handler exists
52
+ * to prevent. Optional; falls back to the plain broadcast for hosts that do
53
+ * not filter.
54
+ */
55
+ broadcastEveryone?: (message: OutboundMessage) => void;
44
56
  }
45
57
  export declare function createProjectHandlers(ctx: ProjectHandlersContext): ProjectRouteHandlers;
46
58
  export {};
@@ -3,19 +3,27 @@
3
3
  * files are created, modified, or deleted inside the project root, so
4
4
  * the WebUI file explorer refreshes its tree without manual navigation.
5
5
  *
6
- * Uses Node's native `fs.watch` with `recursive: true` (supported on
7
- * Windows and macOS; Linux support landed in Node 22). Events are
8
- * debounced (400ms) to coalesce bursts from build tools, git
9
- * checkouts, and agent writes. Heavyweight directories
10
- * (`node_modules`, `.git`, `dist`, …) are ignored to avoid event
11
- * storms the same `SKIP_DIRS` set the tree builder uses.
6
+ * Subscribes to core's SHARED recursive project watcher rather than opening
7
+ * its own. This file used to call `fs.watch(root, {recursive:true})` directly,
8
+ * which meant a running WebUI put a SECOND recursive watch on the same tree:
9
+ * the OS then tracked it independently and delivered every event twice — and
10
+ * on Windows `ReadDirectoryChangesW` reports `node_modules` too, so a `pnpm
11
+ * install` or a build paid that twice over. Core's registry keeps ONE watcher
12
+ * per resolved root per process and fans out to subscribers, which is exactly
13
+ * why it exists; the codebase indexer and the chronicle file observer were
14
+ * already using it.
12
15
  *
13
- * The watcher is `persistent: false` and `unref`'d on each handle so
14
- * it does not keep the process alive. The returned disposer closes
15
- * all native handles.
16
+ * Events are debounced (400ms) to coalesce bursts from build tools, git
17
+ * checkouts, and agent writes. Heavyweight directories (`node_modules`,
18
+ * `.git`, `dist`, …) are ignored to avoid event storms — the same `SKIP_DIRS`
19
+ * set the tree builder uses.
20
+ *
21
+ * The shared watcher is non-persistent and never keeps the process alive. The
22
+ * returned disposer drops this subscription; the underlying handle closes once
23
+ * the last subscriber leaves.
16
24
  */
17
25
  import type { ConnectedClient, WSServerMessage } from './types.js';
18
- export interface ProjectWatcherDeps {
26
+ interface ProjectWatcherDeps {
19
27
  /** Absolute project root to watch. */
20
28
  projectRoot: string;
21
29
  /** Broadcast a message to all connected WS clients. */
@@ -28,4 +36,5 @@ export interface ProjectWatcherDeps {
28
36
  * disposer that closes all native watch handles.
29
37
  */
30
38
  export declare function startProjectWatcher(deps: ProjectWatcherDeps): () => void;
39
+ export {};
31
40
  //# sourceMappingURL=project-watcher.d.ts.map
@@ -30,5 +30,4 @@ export interface ProviderServiceContext {
30
30
  broadcastSaved(providers: Record<string, ProviderConfig>): void;
31
31
  }
32
32
  export declare function createProviderServiceContext(deps: ProviderOperationsDeps): ProviderServiceContext;
33
- export type { ProviderServiceContext as ProviderMutationsContext };
34
33
  //# sourceMappingURL=mutations.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Give every conversation the rebuilt provider.
3
+ *
4
+ * A WrongProxy toggle and a credential hot-reload are PROJECT-wide facts: the
5
+ * same provider, rebuilt with a different base URL or a new key. Both used to
6
+ * assign the result to the root context alone.
7
+ *
8
+ * That is enough for a host with one conversation and wrong for four. A
9
+ * session's `Context` is created with the root's provider REFERENCE, so every
10
+ * tab opened before the toggle kept running the old object — routed the old
11
+ * way, authenticated with the old key — while the leader used the new one.
12
+ * Nothing on screen distinguishes them, and the divergence lasts until each
13
+ * tab happens to switch models.
14
+ *
15
+ * The fan-out is deliberately conservative: only contexts still pointing at
16
+ * the exact provider object being replaced are moved. A tab that chose its own
17
+ * model owns its own provider and must keep it — that choice is per
18
+ * conversation, and overwriting it here would be the very cross-tab write this
19
+ * exists to prevent.
20
+ *
21
+ * @module provider-fanout
22
+ */
23
+ import type { Context } from '@wrongstack/core/agent';
24
+ import type { Provider } from '@wrongstack/core/types';
25
+ interface ProviderFanoutInput {
26
+ /** Conversations holding a live agent; absent on single-conversation hosts. */
27
+ sessionAgentIds?: (() => string[]) | undefined;
28
+ /** Read-only agent lookup — must never materialise one. */
29
+ peekAgent?: ((sessionId?: string) => {
30
+ ctx: Context;
31
+ } | undefined) | undefined;
32
+ /** The provider object being replaced. */
33
+ previous: Provider | undefined;
34
+ /** The rebuilt provider. */
35
+ next: Provider;
36
+ /** The context that already received `next` and must not be revisited. */
37
+ applied: Context;
38
+ }
39
+ /**
40
+ * Move every other conversation off `previous` and onto `next`.
41
+ *
42
+ * Returns the session ids that were moved, for logging and tests.
43
+ */
44
+ export declare function fanOutProviderRebuild(input: ProviderFanoutInput): string[];
45
+ export {};
46
+ //# sourceMappingURL=provider-fanout.d.ts.map
@@ -49,7 +49,7 @@ export declare function createProviderOperations(deps: ProviderOperationsDeps):
49
49
  broadcastSaved: (providers: Record<string, ProviderConfig>) => void;
50
50
  loadConfigProviders: () => Promise<Record<string, ProviderConfig>>;
51
51
  };
52
- export interface ProviderHandlerDeps {
52
+ interface ProviderHandlerDeps {
53
53
  /** Path to the active profile config; the only provider mutation target. */
54
54
  profileConfigPath: string;
55
55
  vault: import('@wrongstack/core/types').SecretVault;
@@ -57,7 +57,11 @@ export interface ProviderHandlerDeps {
57
57
  setConfigWriteLock: (lock: Promise<void>) => void;
58
58
  getConfigWriteLock: () => Promise<void>;
59
59
  /** Broadcast a message to all connected WebUI clients */
60
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
60
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
61
+ /** Deliver to the tab that owns this session, overriding the id on the
62
+ * payload. Needed when the payload names a SUBAGENT's session, which no
63
+ * tab subscribes to. */
64
+ targetSessionId?: string) => void;
61
65
  /** Connected WebUI clients map */
62
66
  clients: Map<WebSocket, ConnectedClient>;
63
67
  /** Used by the ChatGPT OAuth flow's tier-2 model lookup (best-effort). */
@@ -48,6 +48,9 @@ export interface ProviderRouteHandlers {
48
48
  /** Live provider/model health tracker (the "waiting room"). Undefined when
49
49
  * the host is not wired with a tracker (e.g. test harnesses). */
50
50
  statusTracker?: ProviderModelStatusTracker | undefined;
51
+ /** Durable block/open audit trail (JSONL) next to the profile config.
52
+ * Undefined when the host did not provide a profile path (test harnesses). */
53
+ providerAuditFile?: string | undefined;
51
54
  }
52
55
  export declare function handleProviderRoute(ws: WebSocket, msg: WSClientMessage, routes: ProviderRouteHandlers): Promise<boolean>;
53
56
  export {};
@@ -1,6 +1,6 @@
1
1
  import type { DefaultSecretVault } from '@wrongstack/core/security';
2
2
  import type { ProviderApiKey, ProviderConfig } from '@wrongstack/core/types';
3
- export interface ProviderStoreDeps {
3
+ interface ProviderStoreDeps {
4
4
  /** Active profile config path. */
5
5
  profileConfigPath: string;
6
6
  vault: DefaultSecretVault;
@@ -24,4 +24,5 @@ export interface ProviderStore {
24
24
  maskedKey(key: string | undefined): string;
25
25
  }
26
26
  export declare function createProviderStore(deps: ProviderStoreDeps): ProviderStore;
27
+ export {};
27
28
  //# sourceMappingURL=provider-store.d.ts.map
@@ -19,8 +19,6 @@
19
19
  */
20
20
  import type * as http from 'node:http';
21
21
  import { type RequirementIntakeService } from '@wrongstack/requirement-intake';
22
- /** Map a domain error to an HTTP status + JSON error body. */
23
- export declare function sendIntakeError(res: http.ServerResponse, error: unknown): void;
24
22
  /**
25
23
  * List intake records for the server's own project. The webui-server serves
26
24
  * one project; the canonical `proj_<ulid>` id lives in `.wrongstack/project.json`,
@@ -2,6 +2,7 @@ import type { WebSocket } from 'ws';
2
2
  import { type AgentRosterRouteHandlers } from './agent-roster-routes.js';
3
3
  import { type AutonomyRouteHandlers } from './autonomy-routes.js';
4
4
  import { type BrainRouteHandlers } from './brain-routes.js';
5
+ import { type ChimeraRouteHandlers } from './chimera-routes.js';
5
6
  import { type ChronicleRouteContext } from './chronicle-routes.js';
6
7
  import { type ClientTransportRouteHandlers } from './client-transport-routes.js';
7
8
  import { type CompletionRouteHandlers } from './completion-routes.js';
@@ -40,6 +41,7 @@ export interface RouteFamilyTable {
40
41
  mode: ModeRouteHandlers;
41
42
  prefs: PrefsRouteHandlers;
42
43
  brain: BrainRouteHandlers;
44
+ chimera: ChimeraRouteHandlers | undefined;
43
45
  worklist: WorklistRouteHandlers;
44
46
  process: ProcessRouteHandlers;
45
47
  host: HostRouteHandlers;
@@ -13,6 +13,7 @@ import type { Config } from '@wrongstack/core/types';
13
13
  import type { MCPRegistry } from '@wrongstack/mcp';
14
14
  import { type AutonomyRouteHandlers } from './autonomy-routes.js';
15
15
  import type { BrainRouteHandlers } from './brain-routes.js';
16
+ import { type ChimeraRouteHandlers } from './chimera-routes.js';
16
17
  import type { CollaborationWebSocketHandler } from './collaboration-ws-handler.js';
17
18
  import type { CustomModeStore } from './custom-context-modes.js';
18
19
  import type { GoalRouteHandlers } from './goal-routes.js';
@@ -20,7 +21,7 @@ import type { GoalWebSocketHandler } from './goal-ws-handler.js';
20
21
  import { type MailboxRouteHandlers } from './mailbox-routes.js';
21
22
  import type { McpRouteHandlers } from './mcp-routes.js';
22
23
  import type { ModeRouteHandlers } from './mode-routes.js';
23
- import type { PendingConfirm } from './pending-confirms.js';
24
+ import { type PendingConfirm } from './pending-confirms.js';
24
25
  import { type PrefsRouteHandlers } from './prefs-routes.js';
25
26
  import type { ProjectRouteHandlers } from './project-routes.js';
26
27
  import type { ProviderRouteHandlers } from './provider-routes.js';
@@ -67,9 +68,17 @@ export interface WebuiMutableState {
67
68
  * refactor moves that into the projectHandlers setter chain so the
68
69
  * route layer just calls a single hook.
69
70
  */
70
- abortRunLock: () => void;
71
- /** True while the leader agent owns the shared run lock. */
72
- isRunActive: () => boolean;
71
+ abortRunLock: (sessionId?: string) => void;
72
+ /** True while `sessionId` (or any session, when omitted) owns a run lock. */
73
+ isRunActive: (sessionId?: string) => boolean;
74
+ /** Every session id with an in-flight run — one per busy WebUI tab. */
75
+ getRunningSessionIds: () => string[];
76
+ /**
77
+ * Host-wide serialiser for session transitions. Shared by the session
78
+ * handlers and the conversation ops so run setup never lands on a context
79
+ * that a concurrent session swap is halfway through re-pointing.
80
+ */
81
+ withSessionTransition: <T>(operation: () => Promise<T>) => Promise<T>;
73
82
  /** Read-only reference to the live WS clients map. */
74
83
  getClients(): Map<WebSocket, ConnectedClient>;
75
84
  }
@@ -80,6 +89,27 @@ export interface WebuiMutableState {
80
89
  export interface WebuiDeps {
81
90
  trustBoundary: import('@wrongstack/core/security').TrustBoundary;
82
91
  agent: Agent;
92
+ getAgent?: ((sessionId?: string) => Agent) | undefined;
93
+ /**
94
+ * The Agent for a session WITHOUT creating one. Read-only callers use this;
95
+ * `getAgent` creates, so asking about a stale id materialises an agent for
96
+ * it and can evict a live tab's.
97
+ */
98
+ peekAgent?: ((sessionId?: string) => Agent | undefined) | undefined;
99
+ /**
100
+ * Every conversation currently holding an agent.
101
+ *
102
+ * A project-wide provider rebuild (WrongProxy toggle, credential
103
+ * hot-reload) used to swap `context.provider` and stop there. Session
104
+ * contexts copy the root's provider REFERENCE when they are created, so
105
+ * every tab opened before the toggle kept the old, unrouted provider while
106
+ * the leader used the new one — four tabs, two different providers, no way
107
+ * to tell from the UI. Hosts with one conversation omit this.
108
+ */
109
+ sessionAgentIds?: (() => string[]) | undefined;
110
+ hasSession?: ((id: string) => boolean) | undefined;
111
+ /** Does this host already hold an open journal writer for that session? */
112
+ isSessionLive?: ((id: string) => boolean) | undefined;
83
113
  context: Context;
84
114
  container: Container;
85
115
  toolRegistry: ToolRegistry;
@@ -149,7 +179,12 @@ export interface WebuiDeps {
149
179
  * delegates without storing state of its own.
150
180
  */
151
181
  export interface WebuiCallbacks {
152
- sessionStartPayload: () => Promise<{
182
+ /**
183
+ * Build a `session.start` payload. `overrides.sessionId` selects WHICH
184
+ * session it describes — with four tabs live, the runtime's own "current"
185
+ * session is not necessarily the one being reported on.
186
+ */
187
+ sessionStartPayload: (overrides?: Record<string, unknown>) => Promise<{
153
188
  sessionId: string;
154
189
  model: string;
155
190
  provider: string;
@@ -186,6 +221,7 @@ export interface AllRoutes {
186
221
  prefsRoutes: PrefsRouteHandlers;
187
222
  autonomyRoutes: AutonomyRouteHandlers;
188
223
  shellGitRoutes: ShellGitRouteHandlers;
224
+ chimeraRoutes: ChimeraRouteHandlers;
189
225
  mailboxRoutes: MailboxRouteHandlers;
190
226
  mcpRoutes: McpRouteHandlers;
191
227
  brainRoutes: BrainRouteHandlers;
@@ -7,7 +7,7 @@ interface SddBoardWSMessage {
7
7
  payload?: Record<string, unknown>;
8
8
  }
9
9
  /** Project paths the handler needs to apply lifecycle ops directly. */
10
- export interface SddBoardLifecycleDeps {
10
+ interface SddBoardLifecycleDeps {
11
11
  projectRoot: string;
12
12
  /** Explicit compatibility hook for file-codec tests and old integrations. */
13
13
  controlTransport?: 'kanban' | 'legacy-file' | undefined;
@@ -20,7 +20,7 @@ export interface SddBoardLifecycleDeps {
20
20
  projectSddBoards: string;
21
21
  };
22
22
  }
23
- export interface SddBoardSecurityDeps {
23
+ interface SddBoardSecurityDeps {
24
24
  trustBoundary: TrustBoundary;
25
25
  logger?: Logger | undefined;
26
26
  }
@@ -46,13 +46,25 @@ interface SessionStartPayloadGetters {
46
46
  getContextMode(): string;
47
47
  getNeedsSetup(): boolean;
48
48
  modelsRegistry: ModelsRegistry;
49
+ /**
50
+ * Resolve a session's own Context. Each WebUI tab runs its own session with
51
+ * its own model, mode and context strategy; without this the payload would
52
+ * describe the runtime's current session for every tab.
53
+ */
54
+ getSessionContext?: ((sessionId: string) => SessionRuntimeContext | undefined) | undefined;
49
55
  }
50
- /**
51
- * Build a factory that produces the rich session.start payload from current
52
- * runtime state. Reads live values through getters so post-/new, post-resume,
53
- * and post-model.switch all broadcast the same shape.
54
- */
55
- export declare function createSessionStartPayload(g: SessionStartPayloadGetters): () => Promise<{
56
+ /** The slice of a session Context this payload reads. */
57
+ interface SessionRuntimeContext {
58
+ model?: string | undefined;
59
+ provider?: {
60
+ id?: string | undefined;
61
+ } | undefined;
62
+ session?: {
63
+ startedAt?: string | undefined;
64
+ } | undefined;
65
+ meta?: Record<string, unknown> | undefined;
66
+ }
67
+ export declare function createSessionStartPayload(g: SessionStartPayloadGetters): (overrides?: Record<string, unknown>) => Promise<{
56
68
  sessionId: string;
57
69
  model: string;
58
70
  provider: string;
@@ -69,6 +81,11 @@ export declare function createSessionStartPayload(g: SessionStartPayloadGetters)
69
81
  protocolCapabilities: string[];
70
82
  needsSetup?: boolean | undefined;
71
83
  reasoningEffortLevels?: string[] | undefined;
84
+ /** Tri-state effort-support signal (see SessionLaneData.effortSupported). */
85
+ effortSupported?: boolean | undefined;
86
+ /** Project-wide effort — display-only hint for the composer's auto option. */
87
+ projectReasoningEffort?: string | undefined;
88
+ [key: string]: unknown;
72
89
  }>;
73
90
  interface WsServerResult {
74
91
  wssPrimary: WebSocketServer;