@wrongstack/webui-server 0.313.0 → 0.316.1

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 +4739 -1302
  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 +4323 -1086
  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 +15 -14
@@ -0,0 +1,145 @@
1
+ /**
2
+ * session-agent-registry.ts — one Agent per open tab.
3
+ *
4
+ * `Agent.run()` refuses to run twice on the same instance, and it is right to:
5
+ * a run mutates `ctx.signal`, `ctx.messages`, the session writer, token
6
+ * bookkeeping and compaction state, none of which survive two runs racing
7
+ * through them. So "four tabs running at once" is not a matter of relaxing that
8
+ * guard — it is a matter of there being four Agents.
9
+ *
10
+ * The embedded WebUI host had exactly one, shared by every tab, and handed it
11
+ * out no matter which session asked. Starting a run in a second tab therefore
12
+ * hit the guard head-on:
13
+ *
14
+ * [agent.run] Agent.run() is already in progress on this instance.
15
+ *
16
+ * This registry is the missing half. It hands each session its own Agent,
17
+ * cloned from the leader's wiring — same container, tools, providers, events,
18
+ * pipelines and executor, its own `Context`. Everything shared is deliberately
19
+ * shared (the tool registry and provider registry are stateless surfaces; the
20
+ * event bus is how the host observes every tab). Everything per-run is not.
21
+ */
22
+ import { Agent } from '@wrongstack/core/agent';
23
+ import type { ModelsRegistry, TokenCounter } from '@wrongstack/core/types';
24
+ export interface SessionAgentRegistryOptions {
25
+ /**
26
+ * The leader Agent. Supplies both the wiring every session agent inherits
27
+ * and the identity of the boot session, which keeps using it.
28
+ */
29
+ template: Agent;
30
+ /** Hard ceiling on live agents. Matches the four-tab ceiling. */
31
+ maxAgents?: number | undefined;
32
+ /**
33
+ * Is this session mid-run? Consulted before eviction — a running agent owns
34
+ * a live transcript that exists nowhere else.
35
+ */
36
+ isRunActive?: ((sessionId: string) => boolean) | undefined;
37
+ /** Called once, right after a new session agent is constructed. */
38
+ onCreate?: ((agent: Agent, sessionId: string) => void) | undefined;
39
+ /** Models registry, so a session's own counter can price its own usage. */
40
+ modelsRegistry?: ModelsRegistry | undefined;
41
+ /**
42
+ * Override how a session's Agent is built. The default clones the template's
43
+ * wiring; a caller that already owns an Agent per session (or a test that
44
+ * does not want a whole container) supplies its own.
45
+ */
46
+ createAgent?: ((sessionId: string) => Agent) | undefined;
47
+ /**
48
+ * Is any connected surface currently showing this session?
49
+ *
50
+ * Consulted before eviction. Without it the victim is simply the oldest
51
+ * non-running entry, which is routinely a tab the user still has open while
52
+ * the agents of tabs they closed minutes ago sit untouched — the open tab
53
+ * then loses its in-memory transcript and comes back empty.
54
+ */
55
+ isDisplayed?: ((sessionId: string) => boolean) | undefined;
56
+ }
57
+ export interface SessionAgentRegistry {
58
+ /**
59
+ * The Agent for a session; the leader's when no session is named.
60
+ *
61
+ * CREATES one when the id is unknown, so this is for callers that own the
62
+ * session (a run, a session transition). Read-only callers must use `peek`:
63
+ * `get` on a stale id materialises an agent and can evict a live one.
64
+ */
65
+ get(sessionId?: string | undefined): Agent;
66
+ /** The Agent for a session, or undefined. Never creates, never evicts. */
67
+ peek(sessionId?: string | undefined): Agent | undefined;
68
+ /**
69
+ * Does this session have an agent with a REAL session writer?
70
+ *
71
+ * A freshly created agent carries a placeholder writer until the session
72
+ * transition that owns the id installs the real one, so `has` alone does not
73
+ * mean the session can be written to.
74
+ */
75
+ isLive(sessionId: string): boolean;
76
+ has(sessionId: string): boolean;
77
+ ids(): string[];
78
+ /** Forget a session's agent, keeping its journal open. */
79
+ drop(sessionId: string): void;
80
+ /**
81
+ * End a session for good: `session_end`, close, forget.
82
+ *
83
+ * What a CLOSED TAB gets. `drop` alone left the journal open with no
84
+ * terminal marker, which is indistinguishable from a journal a crash left
85
+ * hanging — so every tab the user closed on purpose came back on the
86
+ * recovery list, and its history entry had no outcome.
87
+ */
88
+ endAndClose(sessionId: string): Promise<void>;
89
+ /**
90
+ * Drain every open session journal to disk, synchronously.
91
+ *
92
+ * For fatal-exit salvage only (crash-shield, `process.on('exit')`). The
93
+ * per-session writers buffer non-critical records for up to a flush window,
94
+ * and the host's own salvage hook only knows the LEADER's writer — so a
95
+ * crash silently truncated the tail of every other open tab.
96
+ */
97
+ flushAllSync(): void;
98
+ /**
99
+ * End and close every non-leader session journal.
100
+ *
101
+ * Clean shutdown only. Without it, quitting a host with three background
102
+ * tabs left three journals with no trailing `session_end`, which is exactly
103
+ * how a crash looks: the next launch offered all three for recovery. The
104
+ * leader's own journal is left alone — its host teardown owns it.
105
+ */
106
+ closeAll(): Promise<void>;
107
+ }
108
+ /**
109
+ * A token counter that answers for ONE session while still feeding the
110
+ * process-wide one.
111
+ *
112
+ * Four tabs shared a single counter, so every per-session question it was
113
+ * asked came back as the sum of all four: the Inspector's token, cost and
114
+ * cache figures for a tab, the usage stamped into a session's `session_end`
115
+ * record, the before/after numbers on a compaction report. Reads here are this
116
+ * session's own; `account` still forwards, so process-wide consumers (budget
117
+ * watchdog, shutdown totals, project switch) keep seeing everything.
118
+ *
119
+ * Deliberately constructed WITHOUT an event bus: the root counter already
120
+ * emits `token.accounted` for every call, and a second emitter would double
121
+ * every downstream tally.
122
+ */
123
+ export declare function createSessionTokenCounter(opts: {
124
+ root: TokenCounter;
125
+ sessionId: string;
126
+ registry?: ModelsRegistry | undefined;
127
+ providerId?: (() => string | undefined) | undefined;
128
+ }): TokenCounter;
129
+ /**
130
+ * The part of the leader's meta a NEW conversation may inherit.
131
+ *
132
+ * Host-level facts (mode, feature flags, the resolved window size) describe the
133
+ * project, so a fresh tab starts configured rather than bare. The
134
+ * SESSION-SCOPED PREFERENCES are removed, and that is the point: they are
135
+ * per-conversation by design (YOLO, autonomy, the iteration ceiling, the
136
+ * context strategy, the Lite/Standard/Pro identity), and the leader's meta is
137
+ * not the project's copy of them — it is whatever the FIRST TAB last chose.
138
+ * Copying it started a brand-new conversation under another tab's settings,
139
+ * YOLO included. Each of those keys already falls back to the project default
140
+ * when absent, so leaving them off is exactly "starts clean"; the tab's own
141
+ * first choice writes its own.
142
+ */
143
+ export declare function inheritedSessionMeta(rootMeta: Record<string, unknown>): Record<string, unknown>;
144
+ export declare function createSessionAgentRegistry(opts: SessionAgentRegistryOptions): SessionAgentRegistry;
145
+ //# sourceMappingURL=session-agent-registry.d.ts.map
@@ -0,0 +1,49 @@
1
+ import type { SessionEvent } from '@wrongstack/core/types';
2
+ /** One transcript line, as `AgentMonitorService` stores it. */
3
+ export interface AgentTranscriptLine {
4
+ id: string;
5
+ subagentId: string;
6
+ agentName: string;
7
+ ts: string;
8
+ kind: string;
9
+ content: string;
10
+ iteration: number;
11
+ toolName?: string | undefined;
12
+ toolOk?: boolean | undefined;
13
+ costUsd?: number | undefined;
14
+ }
15
+ /** Structural shape of `AgentMonitorService.loadSessionsFromDisk`'s rows. */
16
+ export interface AgentVirtualSessionLike {
17
+ subagentId: string;
18
+ agentName?: string | undefined;
19
+ status?: string | undefined;
20
+ task?: string | undefined;
21
+ transcript?: AgentTranscriptLine[] | undefined;
22
+ }
23
+ /** What `session.start` carries for one subagent. */
24
+ export interface AgentSessionPayload {
25
+ subagentId: string;
26
+ agentName?: string | undefined;
27
+ status?: string | undefined;
28
+ task?: string | undefined;
29
+ role?: string | undefined;
30
+ provider?: string | undefined;
31
+ model?: string | undefined;
32
+ startedAt?: string | undefined;
33
+ endedAt?: string | undefined;
34
+ transcript?: AgentTranscriptLine[] | undefined;
35
+ }
36
+ export type LoadAgentSessions = (subagentIds: readonly string[]) => Promise<AgentVirtualSessionLike[]>;
37
+ /**
38
+ * Build the `agentSessions` field of a `session.start` payload.
39
+ *
40
+ * Returns an empty array — never a partial one — when the journal names no
41
+ * agents, so a session that never delegated costs nothing.
42
+ *
43
+ * The journal's status wins over the monitor's. The monitor reports
44
+ * `'restored'` for anything it read back from disk, which says only "this
45
+ * process did not watch it run"; the journal knows whether the agent finished,
46
+ * failed, or was still going when the file ended.
47
+ */
48
+ export declare function buildAgentSessionsPayload(events: readonly SessionEvent[] | undefined, load: LoadAgentSessions | undefined): Promise<AgentSessionPayload[]>;
49
+ //# sourceMappingURL=session-agent-sessions.d.ts.map
@@ -2,11 +2,11 @@ import type { Logger } from '@wrongstack/core/types';
2
2
  import { type SessionDeletionContext } from './session-deletion.js';
3
3
  export declare const DEFAULT_EMPTY_SESSION_CLEANUP_INTERVAL_MS: number;
4
4
  export declare const EMPTY_SESSION_CLEANUP_INTERVAL_ENV = "WRONGSTACK_EMPTY_SESSION_CLEANUP_INTERVAL_MS";
5
- export interface EmptySessionCleanupContext extends SessionDeletionContext {
5
+ interface EmptySessionCleanupContext extends SessionDeletionContext {
6
6
  hasParticipants: (sessionId: string) => boolean;
7
7
  logger: Pick<Logger, 'info' | 'error'>;
8
8
  }
9
- export interface EmptySessionCleanupResult {
9
+ interface EmptySessionCleanupResult {
10
10
  deleted: number;
11
11
  errors: number;
12
12
  }
@@ -23,4 +23,5 @@ export declare function scheduleOwnerlessEmptySessionCleanup(ctx: EmptySessionCl
23
23
  dispose: () => Promise<void>;
24
24
  runNow: () => Promise<EmptySessionCleanupResult>;
25
25
  };
26
+ export {};
26
27
  //# sourceMappingURL=session-cleanup-scheduler.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import type { SessionStore } from '@wrongstack/core/types';
2
2
  export interface SessionDeletionContext {
3
3
  getActiveSessionId: () => string;
4
+ getActiveSessionIds?: (() => string[]) | undefined;
4
5
  getSessionStore: () => SessionStore;
5
6
  refreshSessions: () => Promise<void>;
6
7
  }
@@ -9,13 +9,14 @@
9
9
  * as getters/setters so this stays a pure function of its context. Handler
10
10
  * bodies are a verbatim lift — only dependency references changed.
11
11
  */
12
- import type { Context } from '@wrongstack/core/agent';
12
+ import type { Agent, Context } from '@wrongstack/core/agent';
13
13
  import type { createStrategyCompactor } from '@wrongstack/core/execution';
14
14
  import type { EventBus } from '@wrongstack/core/kernel';
15
15
  import type { ToolRegistry } from '@wrongstack/core/registry';
16
16
  import type { SessionStore, TokenCounter } from '@wrongstack/core/types';
17
17
  import type { WebSocket } from 'ws';
18
18
  import type { CustomModeStore } from './custom-context-modes.js';
19
+ import type { LoadAgentSessions } from './session-agent-sessions.js';
19
20
  import type { SessionRouteHandlers } from './session-routes.js';
20
21
  import type { SessionIdentityTarget } from './standalone-session-identity.js';
21
22
  import type { ConnectedClient } from './types.js';
@@ -26,6 +27,7 @@ type OutboundMessage = {
26
27
  };
27
28
  type SessionStartPayload = {
28
29
  sessionId: string;
30
+ startedAt?: string | undefined;
29
31
  model: string;
30
32
  provider: string;
31
33
  maxContext: number;
@@ -38,6 +40,26 @@ type SessionStartPayload = {
38
40
  mode: string;
39
41
  contextMode: string;
40
42
  };
43
+ /**
44
+ * Every session a connected surface is currently displaying.
45
+ *
46
+ * `client.sessionId` alone is the tab the connection last acted on; a WebUI
47
+ * page holds up to four on one socket and declares them with
48
+ * `session.subscribe`. Deleting or garbage-collecting a session that a
49
+ * background tab is showing is the same class of bug as broadcasting to the
50
+ * wrong tab, so both guards read the declared set.
51
+ */
52
+ export declare function collectDisplayedSessionIds(ctx: {
53
+ getSession: () => {
54
+ id: string;
55
+ };
56
+ clients?: {
57
+ values(): Iterable<{
58
+ sessionId?: string | null | undefined;
59
+ sessionIds?: ReadonlySet<string> | undefined;
60
+ }>;
61
+ } | undefined;
62
+ }): string[];
41
63
  export interface SessionHandlersContext {
42
64
  config: {
43
65
  provider: string;
@@ -83,8 +105,67 @@ export interface SessionHandlersContext {
83
105
  /** When sessionId is provided, abort only that session's run; otherwise abort all. */
84
106
  abortActiveRun?: ((sessionId?: string) => void) | undefined;
85
107
  isRunActive?: ((sessionId?: string) => boolean) | undefined;
108
+ hasSession?: ((id: string) => boolean) | undefined;
109
+ /**
110
+ * Does this host ALREADY hold an open writer for this session?
111
+ *
112
+ * Distinct from `hasSession`, which every multi-session host answers `true`
113
+ * to (it only says "this runtime can serve that id"). Re-opening the journal
114
+ * for a session that is already live leaks a second `FileSessionWriter` and
115
+ * file handle onto the same file, and with four tabs that happens on every
116
+ * single tab click. Hosts that cannot tell leave it undefined and keep the
117
+ * old behaviour.
118
+ */
119
+ isSessionLive?: ((id: string) => boolean) | undefined;
120
+ /**
121
+ * Sessions that just stopped being displayed by ANY connection.
122
+ *
123
+ * A closed tab leaves its per-session agent — context, transcript, open
124
+ * journal writer — behind, and nothing ever asked for it again. The host
125
+ * uses this to retire them; it must still refuse to retire one whose run is
126
+ * live, because a background run outlives the tab that started it.
127
+ */
128
+ onSessionsUndisplayed?: ((sessionIds: string[]) => void) | undefined;
129
+ getAgent?: ((sessionId?: string) => Agent) | undefined;
130
+ /**
131
+ * Non-creating registry lookup for READ paths. `getAgent` CREATES on read
132
+ * (and can evict a live tab's agent to make room), so a read such as
133
+ * `context.debug` resolves through this when the host supplies one. A miss
134
+ * here must surface as an explicit "not available" answer — never a silent
135
+ * fallback to the shared root context, which belongs to whichever session
136
+ * the runtime currently points at.
137
+ */
138
+ peekAgent?: ((sessionId?: string) => Agent | undefined) | undefined;
86
139
  sessionStartPayload: (overrides?: Record<string, unknown>) => Promise<SessionStartPayload>;
140
+ systemPrompt?: {
141
+ applyVariant?: (variant: string) => Promise<void>;
142
+ } | undefined;
143
+ /**
144
+ * Host-owned serialiser shared with `createConversationOperations`. When
145
+ * omitted the handlers create a private one, which still orders session
146
+ * transitions against each other but not against run setup.
147
+ */
148
+ withSessionTransition?: (<T>(operation: () => Promise<T>) => Promise<T>) | undefined;
149
+ /**
150
+ * Read back the transcripts of NAMED subagents (see
151
+ * `session-agent-sessions.ts`). Hosts that run a fleet supply it; hosts that
152
+ * do not leave it undefined and their replays simply carry no subagents.
153
+ */
154
+ loadAgentSessions?: LoadAgentSessions | undefined;
87
155
  }
156
+ /**
157
+ * Serialiser for operations that re-point a session's runtime context.
158
+ *
159
+ * Session transitions (session.new / session.resume) swap writers, contexts
160
+ * and todo sidecars in several steps. A turn that begins mid-swap would read
161
+ * a half-applied context, so `user_message` setup runs through the SAME gate
162
+ * — which is why this is created once per host and shared, rather than being
163
+ * private to the session handlers.
164
+ *
165
+ * Only the SETUP is gated; agent runs proceed outside it so four tabs still
166
+ * stream concurrently.
167
+ */
168
+ export declare function createSessionTransitionGate(): <T>(operation: () => Promise<T>) => Promise<T>;
88
169
  export declare function createSessionHandlers(ctx: SessionHandlersContext): SessionRouteHandlers;
89
170
  export {};
90
171
  //# sourceMappingURL=session-handlers.d.ts.map
@@ -27,19 +27,19 @@ export interface SessionHistoryWireEntry {
27
27
  outcome?: SessionSummary['outcome'];
28
28
  isCurrent: boolean;
29
29
  }
30
- export interface SessionInspectEvent {
30
+ interface SessionInspectEvent {
31
31
  ts: string;
32
32
  type: SessionEvent['type'];
33
33
  label: string;
34
34
  detail: string;
35
35
  }
36
- export interface SessionInspectFileEntry {
36
+ interface SessionInspectFileEntry {
37
37
  operation: string;
38
38
  filePath: string;
39
39
  toolName: string;
40
40
  ts: string;
41
41
  }
42
- export interface SessionInspectPayload {
42
+ interface SessionInspectPayload {
43
43
  id: string;
44
44
  title: string;
45
45
  name?: string | undefined;
@@ -70,4 +70,5 @@ export declare function buildInspectPayload(summary: SessionSummary | undefined,
70
70
  }): SessionInspectPayload;
71
71
  export declare function toSessionHistoryEntry(summary: SessionSummary, currentSessionId: string): SessionHistoryWireEntry;
72
72
  export declare function toSessionHistoryEntries(summaries: SessionSummary[], currentSessionId: string): SessionHistoryWireEntry[];
73
+ export {};
73
74
  //# sourceMappingURL=session-history.d.ts.map
@@ -22,6 +22,8 @@ export interface SessionRouteHandlers {
22
22
  inspectSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
23
23
  listCheckpoints: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
24
24
  rewindSession: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
25
+ /** Declare which sessions this connection is displaying (its open tabs). */
26
+ subscribeSessions: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
25
27
  }
26
28
  export declare function handleSessionRoute(ws: WebSocket, msg: WSClientMessage, handlers: SessionRouteHandlers): Promise<boolean>;
27
29
  //# sourceMappingURL=session-routes.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The preference keys that belong to a CONVERSATION, not the project.
3
+ *
4
+ * A leaf module on purpose: this is a plain constant that both the preference
5
+ * handlers and the per-tab agent registry need, and importing it from
6
+ * `prefs-handlers.ts` dragged that module's dependencies (`@wrongstack/tools`
7
+ * among them) into every consumer's module graph.
8
+ *
9
+ * @module session-scoped-prefs
10
+ */
11
+ /**
12
+ * Preferences that belong to ONE session rather than to the process.
13
+ *
14
+ * Every WebUI tab runs its own session with its own agent context, so these
15
+ * have to land on the calling tab's meta bag: flipping yolo in tab 3 must not
16
+ * hand tab 1's run blanket tool approval, and a context strategy chosen for a
17
+ * long refactor tab must not re-shape a quick question in the tab beside it.
18
+ *
19
+ * Everything NOT listed here (locale, ports, HQ, telegram, feature flags,
20
+ * breaker, proxy, log level…) is genuinely process-wide and keeps its single
21
+ * global home.
22
+ *
23
+ * The browser half is `SESSION_SCOPED_PREFS` in
24
+ * `webui/src/stores/local-prefs.ts`; the two sets must be identical and
25
+ * `webui/tests/server/session-scoped-prefs-parity.test.ts` enforces it.
26
+ */
27
+ export declare const SESSION_SCOPED_PREF_KEYS: ReadonlySet<string>;
28
+ //# sourceMappingURL=session-scoped-prefs.d.ts.map
@@ -3,13 +3,18 @@ import type { EventBus } from '@wrongstack/core/kernel';
3
3
  import type { WstackPaths } from '@wrongstack/core/utils';
4
4
  import type { WebSocket } from 'ws';
5
5
  import type { ConnectedClient, WSServerMessage } from './types.js';
6
- export interface SetupEventsCoreWatcherDeps {
6
+ interface SetupEventsCoreWatcherDeps {
7
7
  events: EventBus;
8
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
8
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
9
+ /** Deliver to the tab that owns this session, overriding the id on the
10
+ * payload. Needed when the payload names a SUBAGENT's session, which no
11
+ * tab subscribes to. */
12
+ targetSessionId?: string) => void;
9
13
  clients: Map<WebSocket, ConnectedClient>;
10
14
  context: Context;
11
15
  wpaths?: WstackPaths | undefined;
12
16
  }
13
17
  export declare function registerSetupEventsCoreWatchers(deps: SetupEventsCoreWatcherDeps): Array<() => void>;
14
18
  export declare function registerSetupEventsClientStatusWriter(deps: SetupEventsCoreWatcherDeps): () => void;
19
+ export {};
15
20
  //# sourceMappingURL=setup-events-core-watchers.d.ts.map
@@ -2,14 +2,19 @@ import type { Context } from '@wrongstack/core/agent';
2
2
  import type { WstackPaths } from '@wrongstack/core/utils';
3
3
  import type { WebSocket } from 'ws';
4
4
  import type { ConnectedClient, WSServerMessage } from './types.js';
5
- export interface SetupEventsFleetBroadcasterDeps {
5
+ interface SetupEventsFleetBroadcasterDeps {
6
6
  globalConfigPath?: string | undefined;
7
7
  wpaths?: WstackPaths | undefined;
8
8
  context: Context;
9
9
  clients: Map<WebSocket, ConnectedClient>;
10
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
10
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
11
+ /** Deliver to the tab that owns this session, overriding the id on the
12
+ * payload. Needed when the payload names a SUBAGENT's session, which no
13
+ * tab subscribes to. */
14
+ targetSessionId?: string) => void;
11
15
  onFleetBroadcaster?: ((fn: () => Promise<void>) => void) | undefined;
12
16
  isDisposed: () => boolean;
13
17
  }
14
18
  export declare function registerSetupEventsFleetBroadcaster(deps: SetupEventsFleetBroadcasterDeps): (() => void) | undefined;
19
+ export {};
15
20
  //# sourceMappingURL=setup-events-fleet-broadcaster.d.ts.map
@@ -3,7 +3,11 @@ import type { WebSocket } from 'ws';
3
3
  import type { ConnectedClient, WSServerMessage } from './types.js';
4
4
  export declare function registerSetupEventsPatternHandlers(options: {
5
5
  events: EventBus;
6
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
6
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
7
+ /** Deliver to the tab that owns this session, overriding the id on the
8
+ * payload. Needed when the payload names a SUBAGENT's session, which no
9
+ * tab subscribes to. */
10
+ targetSessionId?: string) => void;
7
11
  clients: Map<WebSocket, ConnectedClient>;
8
12
  sessionPayload: <T extends Record<string, unknown>>(payload: T) => T;
9
13
  }): Array<() => void>;
@@ -7,7 +7,7 @@ type SetupEventBroadcast = (clients: Map<WebSocket, ConnectedClient>, msg: WSSer
7
7
  type SessionPayload = <T extends Record<string, unknown>>(payload: T) => T & {
8
8
  sessionId: string;
9
9
  };
10
- export interface SetupEventsProviderHandlersDeps {
10
+ interface SetupEventsProviderHandlersDeps {
11
11
  on: SetupEventOn;
12
12
  broadcast: SetupEventBroadcast;
13
13
  clients: Map<WebSocket, ConnectedClient>;
@@ -1,10 +1,23 @@
1
1
  import type { Context } from '@wrongstack/core/agent';
2
2
  import type { SessionEventBridge } from '@wrongstack/core/storage';
3
- export declare function createSetupEventSessionHelpers(context: Context, sessionBridge: SessionEventBridge | undefined): {
3
+ interface SetupEventSessionHelperOptions {
4
+ /**
5
+ * Resolve an audit bridge bound to ONE session's writer.
6
+ *
7
+ * Without it, audit events can only be appended for the session the runtime
8
+ * is currently on, and a background tab's tool/error history is dropped —
9
+ * the tab keeps working, but resuming it later shows a run with no tool
10
+ * record. Hosts that can address a session's writer (the WebUI, via its
11
+ * per-session agent registry) pass this so every tab journals its own work.
12
+ */
13
+ bridgeForSession?: ((sessionId: string) => SessionEventBridge | undefined) | undefined;
14
+ }
15
+ export declare function createSetupEventSessionHelpers(context: Context, sessionBridge: SessionEventBridge | undefined, options?: SetupEventSessionHelperOptions): {
4
16
  currentSessionId: () => string;
5
17
  sessionPayload: <T extends Record<string, unknown>>(payload: T) => T & {
6
18
  sessionId: string;
7
19
  };
8
20
  appendForCurrentSession: (sessionId: string | undefined, event: Parameters<SessionEventBridge['append']>[0]) => void;
9
21
  };
22
+ export {};
10
23
  //# sourceMappingURL=setup-events-session-helpers.d.ts.map
@@ -3,13 +3,18 @@ import type { WstackPaths } from '@wrongstack/core/utils';
3
3
  import type { WebSocket } from 'ws';
4
4
  import { type FileWatcherMetrics } from './setup-events-watcher.js';
5
5
  import type { ConnectedClient, WSServerMessage } from './types.js';
6
- export interface SetupEventsStatusWatcherDeps {
6
+ interface SetupEventsStatusWatcherDeps {
7
7
  wpaths?: WstackPaths | undefined;
8
8
  watcherMetrics?: FileWatcherMetrics | undefined;
9
9
  clients: Map<WebSocket, ConnectedClient>;
10
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
10
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
11
+ /** Deliver to the tab that owns this session, overriding the id on the
12
+ * payload. Needed when the payload names a SUBAGENT's session, which no
13
+ * tab subscribes to. */
14
+ targetSessionId?: string) => void;
11
15
  on: <E extends EventName>(event: E, listener: Listener<E>) => (() => void) | void;
12
16
  isDisposed: () => boolean;
13
17
  }
14
18
  export declare function registerSetupEventsStatusWatcher(deps: SetupEventsStatusWatcherDeps): (() => void) | undefined;
19
+ export {};
15
20
  //# sourceMappingURL=setup-events-status-watcher.d.ts.map
@@ -5,7 +5,11 @@ import type { SetupEventProjection } from './setup-event-projection.js';
5
5
  import type { ConnectedClient, WSServerMessage } from './types.js';
6
6
  export declare function registerSetupEventsSubagentHandlers(options: {
7
7
  on: <E extends EventName>(event: E, listener: Listener<E>) => void;
8
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
8
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
9
+ /** Deliver to the tab that owns this session, overriding the id on the
10
+ * payload. Needed when the payload names a SUBAGENT's session, which no
11
+ * tab subscribes to. */
12
+ targetSessionId?: string) => void;
9
13
  clients: Map<WebSocket, ConnectedClient>;
10
14
  context: Context;
11
15
  projection?: SetupEventProjection | undefined;
@@ -7,12 +7,25 @@ import type { SetupEventProjection } from './setup-event-projection.js';
7
7
  import type { ConnectedClient, WSServerMessage } from './types.js';
8
8
  export declare function registerSetupEventsToolHandlers(options: {
9
9
  on: <E extends EventName>(event: E, listener: Listener<E>) => void;
10
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
10
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
11
+ /** Deliver to the tab that owns this session, overriding the id on the
12
+ * payload. Needed when the payload names a SUBAGENT's session, which no
13
+ * tab subscribes to. */
14
+ targetSessionId?: string) => void;
11
15
  clients: Map<WebSocket, ConnectedClient>;
12
16
  context: Context;
13
17
  pendingConfirms: Map<string, PendingConfirm>;
14
18
  projection?: SetupEventProjection | undefined;
15
19
  sessionPayload: <T extends Record<string, unknown>>(payload: T) => T;
16
20
  appendForCurrentSession: (sessionId: string | undefined, event: Parameters<SessionEventBridge['append']>[0]) => void;
21
+ /**
22
+ * The Context of a NAMED session, without creating one. The state blocks
23
+ * below (todos / tasks / plan / side effects) read a session's OWN stores;
24
+ * the shared root context's stores belong to whichever tab is foreground
25
+ * right now. Hosts without a per-session registry may omit it — an event
26
+ * naming an unresolvable session then broadcasts no board at all instead of
27
+ * mis-paired data.
28
+ */
29
+ sessionContext?: ((sessionId: string) => Context | undefined) | undefined;
17
30
  }): void;
18
31
  //# sourceMappingURL=setup-events-tool-handlers.d.ts.map
@@ -10,7 +10,11 @@ import type { ConnectedClient, WSServerMessage } from './types.js';
10
10
  export type { FileWatcherMetrics } from './setup-events-watcher.js';
11
11
  export interface SetupEventsDeps {
12
12
  events: EventBus;
13
- broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage) => void;
13
+ broadcast: (clients: Map<WebSocket, ConnectedClient>, msg: WSServerMessage,
14
+ /** Deliver to the tab that owns this session, overriding the id on the
15
+ * payload. Needed when the payload names a SUBAGENT's session, which no
16
+ * tab subscribes to. */
17
+ targetSessionId?: string) => void;
14
18
  clients: Map<WebSocket, ConnectedClient>;
15
19
  config: {
16
20
  tools?: {
@@ -28,6 +32,22 @@ export interface SetupEventsDeps {
28
32
  * with no tool history.
29
33
  */
30
34
  sessionBridge?: SessionEventBridge | undefined;
35
+ /**
36
+ * Resolve an audit bridge for a NAMED session. Hosts that serve several
37
+ * sessions at once (the WebUI, four tabs on one runtime) pass this so a
38
+ * background tab's tool and error history lands in its own journal instead
39
+ * of being dropped for not being the session in front.
40
+ */
41
+ bridgeForSession?: ((sessionId: string) => SessionEventBridge | undefined) | undefined;
42
+ /**
43
+ * The Context of a NAMED session, without creating one.
44
+ *
45
+ * Event payloads describe the session that produced them, and some of what
46
+ * they carry is a per-tab setting — the iteration ceiling above all. Reading
47
+ * it off the shared root context told a background tab it was on "3 / 500"
48
+ * when its own cap was something else entirely.
49
+ */
50
+ sessionContext?: ((sessionId: string) => Context | undefined) | undefined;
31
51
  /** Optional wpaths for writing status.json file. */
32
52
  wpaths?: WstackPaths | undefined;
33
53
  /** Optional live file-watcher metrics sink. */
@@ -14,13 +14,13 @@
14
14
  import type { ProviderRegistry } from '@wrongstack/core/registry';
15
15
  import type { Provider } from '@wrongstack/core/types';
16
16
  import type { Config } from '@wrongstack/core/types';
17
- export interface ResolveSetupProviderOptions {
17
+ interface ResolveSetupProviderOptions {
18
18
  config: Config;
19
19
  /** True when neither provider nor model is set in config. */
20
20
  needsProvider: boolean;
21
21
  providerRegistry: ProviderRegistry;
22
22
  }
23
- export interface ResolvedSetupProvider {
23
+ interface ResolvedSetupProvider {
24
24
  provider: Provider;
25
25
  /**
26
26
  * True when no provider could be resolved and a stub was created — the
@@ -42,4 +42,5 @@ export interface ResolvedSetupProvider {
42
42
  * Throws on provider-construction failure (same as the inline code).
43
43
  */
44
44
  export declare function resolveSetupProvider(opts: ResolveSetupProviderOptions): ResolvedSetupProvider;
45
+ export {};
45
46
  //# sourceMappingURL=setup-screen.d.ts.map
@@ -4,6 +4,10 @@ export interface ShellGitRouteHandlers {
4
4
  gitInfo: (ws: WebSocket) => Promise<void>;
5
5
  gitChanges: (ws: WebSocket) => Promise<void>;
6
6
  gitDiff: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
7
+ gitStage?: ((ws: WebSocket, msg: WSClientMessage) => Promise<void>) | undefined;
8
+ gitUnstage?: ((ws: WebSocket, msg: WSClientMessage) => Promise<void>) | undefined;
9
+ gitDiscard?: ((ws: WebSocket, msg: WSClientMessage) => Promise<void>) | undefined;
10
+ gitCommit?: ((ws: WebSocket, msg: WSClientMessage) => Promise<void>) | undefined;
7
11
  shellOpen: (ws: WebSocket, msg: WSClientMessage) => Promise<void>;
8
12
  }
9
13
  export declare function handleShellGitRoute(ws: WebSocket, msg: WSClientMessage, handlers: ShellGitRouteHandlers): Promise<boolean>;