aicodeman 1.29.1 → 1.30.0

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 (117) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/reboot-restore.d.ts +146 -0
  4. package/dist/reboot-restore.d.ts.map +1 -0
  5. package/dist/reboot-restore.js +205 -0
  6. package/dist/reboot-restore.js.map +1 -0
  7. package/dist/session-env-clamp.d.ts +80 -0
  8. package/dist/session-env-clamp.d.ts.map +1 -0
  9. package/dist/session-env-clamp.js +96 -0
  10. package/dist/session-env-clamp.js.map +1 -0
  11. package/dist/session.d.ts +9 -0
  12. package/dist/session.d.ts.map +1 -1
  13. package/dist/session.js +12 -0
  14. package/dist/session.js.map +1 -1
  15. package/dist/web/ports/session-port.d.ts +31 -0
  16. package/dist/web/ports/session-port.d.ts.map +1 -1
  17. package/dist/web/public/admin-ui.js.gz +0 -0
  18. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  19. package/dist/web/public/{app.556be563.js → app.6d2dc4e8.js} +2 -2
  20. package/dist/web/public/app.6d2dc4e8.js.br +0 -0
  21. package/dist/web/public/app.6d2dc4e8.js.gz +0 -0
  22. package/dist/web/public/approvals-ui.js.gz +0 -0
  23. package/dist/web/public/constants.258b140f.js.gz +0 -0
  24. package/dist/web/public/cron-ui.js.gz +0 -0
  25. package/dist/web/public/entrance-animations.js.gz +0 -0
  26. package/dist/web/public/home-sessions.js.gz +0 -0
  27. package/dist/web/public/i18n.5f897ed5.js.gz +0 -0
  28. package/dist/web/public/image-input.cd4b97c4.js.gz +0 -0
  29. package/dist/web/public/index.html +25 -6
  30. package/dist/web/public/index.html.br +0 -0
  31. package/dist/web/public/index.html.gz +0 -0
  32. package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
  33. package/dist/web/public/keyboard-accessory.2cf04f17.js.gz +0 -0
  34. package/dist/web/public/mobile-handlers.6f354a87.js.gz +0 -0
  35. package/dist/web/public/mobile-overview.js.gz +0 -0
  36. package/dist/web/public/{mobile.e9d0b53e.css → mobile.6caaa28a.css} +1 -1
  37. package/dist/web/public/mobile.6caaa28a.css.br +0 -0
  38. package/dist/web/public/mobile.6caaa28a.css.gz +0 -0
  39. package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
  40. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  41. package/dist/web/public/panels-ui.5b07ad14.js.gz +0 -0
  42. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  43. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  44. package/dist/web/public/readmymind-ui.js.gz +0 -0
  45. package/dist/web/public/reboot-restore-ui.js +142 -0
  46. package/dist/web/public/reboot-restore-ui.js.br +0 -0
  47. package/dist/web/public/reboot-restore-ui.js.gz +0 -0
  48. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  49. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  50. package/dist/web/public/session-lineage.js.gz +0 -0
  51. package/dist/web/public/session-ui.42b81477.js.gz +0 -0
  52. package/dist/web/public/settings-ui.58f1d756.js.gz +0 -0
  53. package/dist/web/public/styles.e6edfb0b.css +1 -0
  54. package/dist/web/public/styles.e6edfb0b.css.br +0 -0
  55. package/dist/web/public/styles.e6edfb0b.css.gz +0 -0
  56. package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
  57. package/dist/web/public/sw.js.gz +0 -0
  58. package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
  59. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js +1 -0
  60. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.br +0 -0
  61. package/dist/web/public/terminal-keycode229-recovery.fb91b25b.js.gz +0 -0
  62. package/dist/web/public/{terminal-ui.38c49244.js → terminal-ui.4f8d5820.js} +2 -2
  63. package/dist/web/public/terminal-ui.4f8d5820.js.br +0 -0
  64. package/dist/web/public/terminal-ui.4f8d5820.js.gz +0 -0
  65. package/dist/web/public/ultracode-panel.js.gz +0 -0
  66. package/dist/web/public/ultracode-windows.js.gz +0 -0
  67. package/dist/web/public/upload.html.gz +0 -0
  68. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  69. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  70. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  71. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  72. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  73. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  74. package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
  75. package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
  76. package/dist/web/public/vendor/xterm.css.gz +0 -0
  77. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  78. package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
  79. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  80. package/dist/web/public/webview-tabs.js.gz +0 -0
  81. package/dist/web/reboot-restore-registry.d.ts +107 -0
  82. package/dist/web/reboot-restore-registry.d.ts.map +1 -0
  83. package/dist/web/reboot-restore-registry.js +189 -0
  84. package/dist/web/reboot-restore-registry.js.map +1 -0
  85. package/dist/web/routes/index.d.ts +1 -0
  86. package/dist/web/routes/index.d.ts.map +1 -1
  87. package/dist/web/routes/index.js +1 -0
  88. package/dist/web/routes/index.js.map +1 -1
  89. package/dist/web/routes/reboot-restore-routes.d.ts +30 -0
  90. package/dist/web/routes/reboot-restore-routes.d.ts.map +1 -0
  91. package/dist/web/routes/reboot-restore-routes.js +273 -0
  92. package/dist/web/routes/reboot-restore-routes.js.map +1 -0
  93. package/dist/web/routes/session-routes.d.ts +1 -18
  94. package/dist/web/routes/session-routes.d.ts.map +1 -1
  95. package/dist/web/routes/session-routes.js +1 -65
  96. package/dist/web/routes/session-routes.js.map +1 -1
  97. package/dist/web/schemas.d.ts +11 -0
  98. package/dist/web/schemas.d.ts.map +1 -1
  99. package/dist/web/schemas.js +13 -0
  100. package/dist/web/schemas.js.map +1 -1
  101. package/dist/web/server.d.ts +74 -0
  102. package/dist/web/server.d.ts.map +1 -1
  103. package/dist/web/server.js +238 -2
  104. package/dist/web/server.js.map +1 -1
  105. package/package.json +1 -1
  106. package/dist/web/public/app.556be563.js.br +0 -0
  107. package/dist/web/public/app.556be563.js.gz +0 -0
  108. package/dist/web/public/mobile.e9d0b53e.css.br +0 -0
  109. package/dist/web/public/mobile.e9d0b53e.css.gz +0 -0
  110. package/dist/web/public/styles.6add175d.css +0 -1
  111. package/dist/web/public/styles.6add175d.css.br +0 -0
  112. package/dist/web/public/styles.6add175d.css.gz +0 -0
  113. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js +0 -1
  114. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.br +0 -0
  115. package/dist/web/public/terminal-keycode229-recovery.6ea8fe37.js.gz +0 -0
  116. package/dist/web/public/terminal-ui.38c49244.js.br +0 -0
  117. package/dist/web/public/terminal-ui.38c49244.js.gz +0 -0
@@ -0,0 +1,273 @@
1
+ /**
2
+ * @fileoverview Reboot-restore routes: offer back the sessions a host reboot destroyed.
3
+ *
4
+ * The boot pass leaves a plan in `web/reboot-restore-registry` when the machine
5
+ * plausibly rebooted. The board reads it, shows a banner, and the user decides:
6
+ * - `GET /api/reboot-restore`: what is on offer, ownership-scoped
7
+ * - `POST /api/reboot-restore/restore`: rebuild some or all of it
8
+ * - `POST /api/reboot-restore/dismiss`: drop the offer
9
+ *
10
+ * A click, not the heuristic, is what creates panes. The heuristic only decides
11
+ * whether the banner appears, so a wrong yes costs a line of text the user
12
+ * dismisses rather than N CLI processes nobody asked for.
13
+ *
14
+ * Rebuilding is take-then-build: entries leave the plan synchronously at the top
15
+ * of the route, before the first `await`, and the whole route is single-flighted,
16
+ * so a double-click or two devices cannot put two panes on one conversation.
17
+ * Three things are re-checked at click time rather than trusted from boot: the
18
+ * owner's privilege grant, the workspace still being on disk, and the
19
+ * conversation not already being live because the user resumed it by hand.
20
+ *
21
+ * A rebuilt session comes back attached, idle and disarmed. Respawn controllers
22
+ * and Ralph loops are deliberately not re-armed, and its terminal scrollback is
23
+ * gone, because the pane is new. The banner says so.
24
+ */
25
+ import { existsSync } from 'node:fs';
26
+ import { ApiErrorCode, createErrorResponse, getErrorMessage } from '../../types.js';
27
+ import { RebootRestoreRequestSchema } from '../schemas.js';
28
+ import { parseBody, getAuthUser, canAccessOwned, ownerFor, isWorkingDirAllowedForUsername, sessionCapacityMessage, } from '../route-helpers.js';
29
+ import { rebootRestoreRegistry } from '../reboot-restore-registry.js';
30
+ import { rejectAlreadyLive } from '../../reboot-restore.js';
31
+ import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
32
+ import { Session } from '../../session.js';
33
+ import { resolveClaudeModeForUsername } from '../../user-store.js';
34
+ import { getCli } from '../../config/cli-registry/registry.js';
35
+ import { applyWorkspaceHooks, seedAgentSessionPreamble } from '../../hooks-config.js';
36
+ import { getLifecycleLog } from '../../session-lifecycle-log.js';
37
+ import { STATS_COLLECTION_INTERVAL_MS } from '../../config/server-timing.js';
38
+ import { SseEvent } from '../sse-events.js';
39
+ /** The banner's view of one restorable session. The record itself never leaves the server. */
40
+ function toBannerItem(entry) {
41
+ return {
42
+ id: entry.sessionId,
43
+ name: entry.name,
44
+ workingDir: entry.workingDir,
45
+ mode: entry.mode,
46
+ owner: entry.owner,
47
+ };
48
+ }
49
+ export function registerRebootRestoreRoutes(app, ctx) {
50
+ const accessorFor = (req) => {
51
+ const user = getAuthUser(req);
52
+ return (owner) => canAccessOwned(user, owner);
53
+ };
54
+ // ========== What is on offer ==========
55
+ app.get('/api/reboot-restore', async (req) => {
56
+ const entries = rebootRestoreRegistry.list(accessorFor(req));
57
+ return {
58
+ sessions: entries.map(toBannerItem),
59
+ // Said plainly here so the banner never implies a full restore: the pane is
60
+ // new, so the conversation continues and the terminal history does not.
61
+ scrollbackRestored: false,
62
+ };
63
+ });
64
+ // ========== Spend it ==========
65
+ app.post('/api/reboot-restore/restore', async (req, reply) => {
66
+ const body = parseBody(RebootRestoreRequestSchema, req.body, 'Invalid reboot restore request');
67
+ const canAccess = accessorFor(req);
68
+ const owner = ownerFor(req);
69
+ // Take BEFORE the first await: a second click must find nothing to spend.
70
+ // The flight is per owner, because `take()` already guarantees two callers
71
+ // never receive the same entry, so one user's restore need not block another's.
72
+ if (!rebootRestoreRegistry.beginSpending(owner)) {
73
+ return reply.code(409).send(createErrorResponse(ApiErrorCode.CONFLICT, 'A reboot restore is already running'));
74
+ }
75
+ const taken = rebootRestoreRegistry.take(canAccess, body.sessionIds, owner);
76
+ // Entries nothing built a pane for, returned to the plan on every exit path
77
+ // including a throw. Without this a failure between here and the loop would
78
+ // spend the offer and rebuild nothing, and the plan cannot be rebuilt.
79
+ const unspent = new Set(taken);
80
+ try {
81
+ if (taken.length === 0)
82
+ return { restored: [], skipped: [] };
83
+ // The plan was built at boot and the board has moved on since. A conversation
84
+ // the user resumed by hand from the Resume list is already on screen, and a
85
+ // second pane on it would fight the first for the same transcript. This one
86
+ // is never re-offered: unlike a missing workspace, it cannot stop being true.
87
+ // Read fresh each time rather than snapshotted once: the loop below awaits a
88
+ // real `startInteractive()` per entry, so by the tenth entry a snapshot taken
89
+ // here is tens of seconds old, and a conversation the user resumed by hand in
90
+ // that window would be invisible to it.
91
+ const liveSessionIds = () => new Set(ctx.sessions.keys());
92
+ const liveConversationIds = () => new Set([...ctx.sessions.values()].map((session) => session.claudeSessionId).filter((id) => !!id));
93
+ const { restore, skipped } = rejectAlreadyLive(taken, liveSessionIds(), liveConversationIds());
94
+ for (const entry of taken) {
95
+ if (skipped.some((s) => s.sessionId === entry.sessionId))
96
+ unspent.delete(entry);
97
+ }
98
+ const restored = [];
99
+ const failures = [...skipped];
100
+ const workspaceHooksEnabled = await ctx.getWorkspaceHooksEnabled();
101
+ for (const entry of restore) {
102
+ // The already-live check, re-run against the board as it is NOW. The pass
103
+ // above decided the batch; this catches a conversation that went live while
104
+ // an earlier entry in this same batch was starting. Spent rather than
105
+ // returned to the plan, for the same reason as the batch pass: unlike a
106
+ // missing workspace or a withdrawn grant, an open conversation is not a
107
+ // condition that stops being true.
108
+ const [lateLive] = rejectAlreadyLive([entry], liveSessionIds(), liveConversationIds()).skipped;
109
+ if (lateLive) {
110
+ failures.push(lateLive);
111
+ unspent.delete(entry);
112
+ continue;
113
+ }
114
+ // Capacity is re-checked per iteration, because this loop is itself
115
+ // creating the sessions it counts. The offer can be a day old, so the
116
+ // board may be fuller now than the plan assumed.
117
+ const capMsg = sessionCapacityMessage(ctx.sessions, entry.owner);
118
+ if (capMsg) {
119
+ failures.push({ sessionId: entry.sessionId, reason: 'capacity-reached' });
120
+ continue;
121
+ }
122
+ // A repo can be deleted between the boot that planned this and the click.
123
+ if (!existsSync(entry.workingDir)) {
124
+ failures.push({ sessionId: entry.sessionId, reason: 'workspace-missing' });
125
+ continue;
126
+ }
127
+ // Multi-user workspace separation: the create route confines a non-admin's
128
+ // workingDir to their own case space, and a grant can be withdrawn between
129
+ // the session's creation and this restore, so the confinement is re-run
130
+ // rather than inherited from the record. Keyed on the OWNER, not on the
131
+ // caller: an admin spending another user's entry must be held to that
132
+ // user's confinement, and `isWorkingDirAllowed` would wave an admin
133
+ // through. The same reason the two grant re-checks below read
134
+ // `saved.owner`.
135
+ if (!(await isWorkingDirAllowedForUsername(entry.owner, entry.workingDir))) {
136
+ // Left on offer: a withdrawn grant can be restored, unlike an already-open
137
+ // conversation, so this is not the permanent kind of refusal.
138
+ failures.push({ sessionId: entry.sessionId, reason: 'workspace-forbidden' });
139
+ continue;
140
+ }
141
+ try {
142
+ const saved = entry.state;
143
+ const claudeModeConfig = await ctx.getClaudeModeConfig();
144
+ const session = new Session({
145
+ // The old id is reused on purpose: a pinned record, subagent parents,
146
+ // window states and the lifecycle log all key off it, and the unpinned
147
+ // record is gone, so there is nothing to collide with.
148
+ id: saved.id,
149
+ workingDir: saved.workingDir,
150
+ mode: saved.mode,
151
+ name: saved.name,
152
+ // Without this the constructor re-infers ownership from the name, so a
153
+ // session the user renamed by hand to something shaped like `w<n>-<case>`
154
+ // comes back as `placeholder` and auto-naming overwrites their name on
155
+ // the next prompt. The route persists below, so the loss would go to
156
+ // disk. `restoreMuxSessions()` passes it for the same reason.
157
+ nameSource: saved.nameSource,
158
+ createdAt: saved.createdAt,
159
+ mux: ctx.mux,
160
+ useMux: true,
161
+ // No `muxSession`: the reboot took the pane with it, so `startInteractive()`
162
+ // takes its create branch and makes a fresh one.
163
+ claudeMode: await resolveClaudeModeForUsername(claudeModeConfig.claudeMode, saved.owner),
164
+ allowedTools: claudeModeConfig.allowedTools,
165
+ resumeSessionId: entry.resumeConversationId,
166
+ // Re-resolved against the owner's CURRENT grant, never replayed from the
167
+ // record: a grant held when the record was written may be gone now.
168
+ envOverrides: await clampEnvOverridesForOwner(saved.owner, saved.__envOverrides),
169
+ effort: saved.effort,
170
+ attachmentHistory: saved.__attachmentHistory ??
171
+ saved.attachmentHistory,
172
+ lastSubmitAt: saved.lastSubmitAt,
173
+ claudeSessionChain: saved.claudeSessionChain,
174
+ lastActivityAt: saved.lastActivityAt,
175
+ owner: saved.owner,
176
+ parentSessionId: saved.parentSessionId,
177
+ });
178
+ await ctx.addSession(session);
179
+ // Before the listeners, because setupSessionListeners() reads the
180
+ // image-watcher flag this phase restores; before the spawn, because the
181
+ // custom-model environment and the nice priority shape the process.
182
+ await ctx.reapplyPersistedSessionState(session, saved, 'before-spawn');
183
+ await ctx.setupSessionListeners(session);
184
+ await session.startInteractive();
185
+ // The session's own history, applied only once the pane exists: on a
186
+ // failed start these totals would belong to a session that never ran.
187
+ // Both halves precede the route's OWN persist, which matters because a
188
+ // constructed session carries none of this and `toState()` is written
189
+ // wholesale, so persisting first would replace the fuller record with
190
+ // the reduced one and drop the pin that keeps it from being pruned. A
191
+ // listener-driven persist can still land inside the debounce window
192
+ // while the pane starts; the write below repairs the record.
193
+ // `rearmAutoResumeSchedule: false`: the saved stamp predates the reboot and
194
+ // the pane is new, so honouring it would have every restored session type
195
+ // `continue` into itself about a minute after one click. Auto-resume stays
196
+ // enabled and re-arms on the next real limit message. This is also what the
197
+ // module header promises ("comes back attached, idle and disarmed").
198
+ await ctx.reapplyPersistedSessionState(session, saved, 'after-spawn', {
199
+ rearmAutoResumeSchedule: false,
200
+ });
201
+ ctx.persistSessionState(session);
202
+ // A session without its workspace hooks goes silently blind: no stop or
203
+ // idle events for respawn, no Approvals Inbox item, no red tab on a
204
+ // blocking dialog. The boot-time sweep finished hours ago, so the click
205
+ // path installs them itself. `hooks: 'always'` is the capability that says
206
+ // this CLI installs Codeman's hooks into the workspace.
207
+ if (workspaceHooksEnabled && getCli(session.mode)?.capabilities.hooks === 'always') {
208
+ await applyWorkspaceHooks(session.workingDir, true).catch((err) => console.warn(`[reboot-restore] hook install failed for ${session.workingDir}: ${getErrorMessage(err)}`));
209
+ }
210
+ // Both create paths seed this; without it a restored claude session's agent
211
+ // skill falls back to writing out the whole ~150-line §0 preamble. Remote and
212
+ // docker sessions never reach here (the plan rejects them as
213
+ // `remote-or-docker`), so the local-only condition is structural.
214
+ if (getCli(session.mode)?.capabilities.agentSkillInjection && (await ctx.getAgentSkillEnabled())) {
215
+ await seedAgentSessionPreamble(session.id).catch((err) => console.warn(`[agent-skill] preamble seed failed for ${session.id}: ${getErrorMessage(err)}`));
216
+ }
217
+ getLifecycleLog().log({ event: 'recovered', sessionId: session.id, name: session.name });
218
+ // Every other open tab and phone needs this; the clicking tab already has
219
+ // the response, and the client's handler is an idempotent upsert.
220
+ ctx.broadcast(SseEvent.SessionCreated, ctx.getSessionStateWithRespawn(session));
221
+ restored.push(toBannerItem(entry));
222
+ }
223
+ catch (err) {
224
+ // One entry that will not start must not stop the rest of the pass, and
225
+ // must not leave a registered session with no pane behind it: by this
226
+ // point the session is in `ctx.sessions`, holds a tab-layout slot and has
227
+ // listeners.
228
+ //
229
+ // Reaching this is rarer than it looks, measured against a real server:
230
+ // the CLI resolver finds its binary by absolute path rather than through
231
+ // PATH, and tmux falls back to another directory rather than failing when
232
+ // it cannot enter the workspace, so neither of the two obvious "freshly
233
+ // booted machine" failures throws. What is left is the mux layer itself
234
+ // failing, which is why this path is defended rather than expected.
235
+ console.error(`[reboot-restore] failed to rebuild ${entry.sessionId}:`, err);
236
+ // Not cleanupSession(): that is the user-initiated delete, and it would
237
+ // count this session's historical tokens into the lifetime totals, demote
238
+ // a pinned record to `stopped` (which this pass reads as an intentional
239
+ // kill, making the session permanently unrestorable) and delete the
240
+ // workspace's `.claude-images`. This undoes only the construction.
241
+ await ctx
242
+ .discardPartiallyBuiltSession(entry.sessionId)
243
+ .catch((discardErr) => console.error(`[reboot-restore] discarding a failed rebuild failed: ${getErrorMessage(discardErr)}`));
244
+ failures.push({ sessionId: entry.sessionId, reason: 'rebuild-failed' });
245
+ // Left on offer: the user can put the binary back and click again.
246
+ continue;
247
+ }
248
+ unspent.delete(entry);
249
+ }
250
+ if (restored.length > 0) {
251
+ // A reboot leaves recovery with nothing alive to find, so its own block never
252
+ // started the stats collector. This clears and re-arms its interval, so it is
253
+ // safe to call whether or not the collector is already running.
254
+ ctx.mux.startStatsCollection(STATS_COLLECTION_INTERVAL_MS);
255
+ }
256
+ return { restored, skipped: failures };
257
+ }
258
+ finally {
259
+ // Anything that never became a pane goes back on offer, including after a
260
+ // throw, so a transient failure costs a retry rather than the whole plan.
261
+ // Ends the flight: entries still parked for it come back if they are in
262
+ // `unspent`, and a Dismiss that unparked them meanwhile wins.
263
+ rebootRestoreRegistry.releaseFlight(owner, [...unspent]);
264
+ rebootRestoreRegistry.endSpending(owner);
265
+ }
266
+ });
267
+ // ========== Drop it ==========
268
+ app.post('/api/reboot-restore/dismiss', async (req) => {
269
+ const dismissed = rebootRestoreRegistry.clear(accessorFor(req));
270
+ return { dismissed };
271
+ });
272
+ }
273
+ //# sourceMappingURL=reboot-restore-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reboot-restore-routes.js","sourceRoot":"","sources":["../../../src/web/routes/reboot-restore-routes.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { FastifyInstance } from 'fastify';
7
7
  import { type SessionMode, type CodexConfig, type GeminiConfig, type AntigravityConfig, type PiConfig, type GrokConfig, type DeepSeekConfig, type OmpConfig } from '../../types.js';
8
+ import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
8
9
  import type { SessionPort, EventPort, ConfigPort, InfraPort, AuthPort, TabLayoutPort } from '../ports/index.js';
9
10
  /**
10
11
  * Strip redundant Ink spinner/status-bar redraw frames from the terminal buffer.
@@ -49,23 +50,6 @@ export declare function _clampExternalCliBypassForOwner(owner: string | undefine
49
50
  grokConfig: GrokConfig | undefined;
50
51
  deepSeekConfig: DeepSeekConfig | undefined;
51
52
  }>;
52
- /**
53
- * Env-var half of the multi-user bypass clamp.
54
- *
55
- * `clampExternalCliBypassForOwner()` clamps the per-CLI CONFIG, and for every CLI
56
- * but DeepSeek that is the whole story. Here it is not: `applyEnvOverrides()` runs
57
- * AFTER `_configureCliEnv()` in tmux-manager, so an override sent on the SAME
58
- * request lands last and wins, and a non-granted owner could restore
59
- * `danger-full-access` on the very request the config clamp downgraded.
60
- *
61
- * Keys are DROPPED rather than rewritten: dropping falls through to what
62
- * `_configureCliEnv()` exports, which is the clamped config and the server's own
63
- * `DSH_HOME`, i.e. exactly the intended state. No-op in single-user mode and for a
64
- * granted owner, like every other clamp here
65
- * (`canUsernameRunPrivilegedCommands()` returns true when `!isMultiUserMode()`),
66
- * and it returns the caller's own object untouched when there is nothing to strip.
67
- */
68
- declare function clampEnvOverridesForOwner(owner: string | undefined, envOverrides: Record<string, string> | undefined): Promise<Record<string, string> | undefined>;
69
53
  /** Test hook: the env-var half of the same multi-user safety gate. */
70
54
  export declare const _clampEnvOverridesForOwner: typeof clampEnvOverridesForOwner;
71
55
  /** Test seam: pane-liveness state is module-level, so a suite must be able to reset it. */
@@ -86,5 +70,4 @@ export declare function _paneDeathWatcherCount(): number;
86
70
  */
87
71
  export declare function resolveOmpConfigForCreate(mode: SessionMode, workingDir: string, ompConfig: OmpConfig | undefined): OmpConfig | undefined;
88
72
  export declare function registerSessionRoutes(app: FastifyInstance, ctx: SessionPort & EventPort & ConfigPort & InfraPort & AuthPort & TabLayoutPort): void;
89
- export {};
90
73
  //# sourceMappingURL=session-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAS7D,OAAO,EAOL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AA+FxB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0EhH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAoED;;;;;;;GAOG;AACH,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,YAAY,EAC3B,iBAAiB,CAAC,EAAE,iBAAiB,EACrC,QAAQ,CAAC,EAAE,QAAQ,EACnB,UAAU,CAAC,EAAE,UAAU,EACvB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC;IACT,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,cAAc,EAAE,cAAc,GAAG,SAAS,CAAC;CAC5C,CAAC,CAiBD;AAuCD;;;;;;;;;;;;;;;GAeG;AACH,iBAAe,yBAAyB,CACtC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC/C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,CAQ7C;AAED,sEAAsE;AACtE,eAAO,MAAM,0BAA0B,kCAA4B,CAAC;AA8LpE,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA2ED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,SAAS,GAAG,SAAS,GAC/B,SAAS,GAAG,SAAS,CAYvB;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAC/E,IAAI,CA00HN"}
1
+ {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAS7D,OAAO,EAOL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AA6DxB,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAmCvE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0EhH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAoED;;;;;;;GAOG;AACH,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,YAAY,EAC3B,iBAAiB,CAAC,EAAE,iBAAiB,EACrC,QAAQ,CAAC,EAAE,QAAQ,EACnB,UAAU,CAAC,EAAE,UAAU,EACvB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC;IACT,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACjD,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,cAAc,EAAE,cAAc,GAAG,SAAS,CAAC;CAC5C,CAAC,CAiBD;AAED,sEAAsE;AACtE,eAAO,MAAM,0BAA0B,kCAA4B,CAAC;AA8LpE,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA2ED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,SAAS,GAAG,SAAS,GAC/B,SAAS,GAAG,SAAS,CAYvB;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAC/E,IAAI,CA00HN"}
@@ -25,6 +25,7 @@ import { clampWaitMs, MAX_BUFFER_SCAN_BYTES } from '../../config/agent-wait.js';
25
25
  import { autoConfigureRalph, canAccessOwned, CASES_DIR, findPersistedSessionOrFail, findSessionOrFail, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, resolveAgentCaseOrigin, resolveCasesDir, resolveParentSessionId, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
26
26
  import { buildAgentCaseMarker, writeAgentCaseMarker } from '../../agent-case-marker.js';
27
27
  import { canUsernameRunPrivilegedCommands, resolveClaudeModeForUsername } from '../../user-store.js';
28
+ import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
28
29
  import { enabledClis, getCli } from '../../config/cli-registry/registry.js';
29
30
  import { resolveCliLaunchError } from '../../utils/cli-launcher.js';
30
31
  import { legacyConfigForMode } from '../../session-cli-registry-bridge.js';
@@ -301,71 +302,6 @@ export async function _clampExternalCliBypassForOwner(owner, codexConfig, gemini
301
302
  });
302
303
  return out;
303
304
  }
304
- /**
305
- * Env-var keys a non-granted owner must not be able to set, because each one
306
- * hands back privilege the config clamp above just removed, or redirects a
307
- * credential-resolution endpoint.
308
- *
309
- * The DeepSeek three are reachable because `DSH_*` and `DEEPSEEK_*` are
310
- * allowlisted `envOverrides` prefixes (schemas.ts) — which they have to be, since
311
- * that is also how a user configures the harness's non-privileged knobs.
312
- *
313
- * - `DSH_PERMISSION_MODE` IS the harness's permission switch. Every other CLI's
314
- * bypass is a command-line FLAG, reachable only through the per-CLI config the
315
- * clamp already owns; this one is an env var, so the config clamp alone is
316
- * half a gate.
317
- * - `DSH_HOME` points the launcher at a profile tree, and a profile's plugin code
318
- * executes at BOOT, before any approval row can apply. A user who can write a
319
- * workspace can put a profile in it, so this is the wider of the two.
320
- * - `DEEPSEEK_BASE_URL` aims the provider endpoint, and `_configureCliEnv()`
321
- * forwards the SERVER's own `DEEPSEEK_API_KEY` into every dsh pane before
322
- * `applyEnvOverrides()` runs — so a non-granted owner who could set the base
323
- * URL would have the operator's API key sent as a bearer credential to a host
324
- * of their choosing. (`DEEPSEEK_API_KEY` itself stays overridable: supplying
325
- * your OWN key removes privilege rather than granting it.)
326
- * - `OMP_AUTH_BROKER_URL`/`OMP_AUTH_BROKER_TOKEN` are where omp resolves
327
- * credentials from — the same shape as `DEEPSEEK_BASE_URL` above, reachable
328
- * because `OMP_*` is an allowlisted prefix. Unlike DeepSeek, Codeman does not
329
- * forward any operator-held key into an omp pane today (omp's provider
330
- * credentials live in `~/.omp` config files, not env vars), so there is no
331
- * known concrete exfiltration path yet — clamped defensively anyway, since a
332
- * non-granted owner redirecting where a shared multi-tenant deployment
333
- * resolves auth from is not something to allow silently (found in
334
- * Ark0N/Codeman#353 review; omp's own knobs are otherwise mostly `PI_*`,
335
- * already allowlisted for pi and not addressed here — see resolveOmpHome()).
336
- */
337
- function ownerClampedEnvKeys() {
338
- return enabledClis().flatMap((entry) => entry.capabilities.privilegedEnvKeys);
339
- }
340
- /**
341
- * Env-var half of the multi-user bypass clamp.
342
- *
343
- * `clampExternalCliBypassForOwner()` clamps the per-CLI CONFIG, and for every CLI
344
- * but DeepSeek that is the whole story. Here it is not: `applyEnvOverrides()` runs
345
- * AFTER `_configureCliEnv()` in tmux-manager, so an override sent on the SAME
346
- * request lands last and wins, and a non-granted owner could restore
347
- * `danger-full-access` on the very request the config clamp downgraded.
348
- *
349
- * Keys are DROPPED rather than rewritten: dropping falls through to what
350
- * `_configureCliEnv()` exports, which is the clamped config and the server's own
351
- * `DSH_HOME`, i.e. exactly the intended state. No-op in single-user mode and for a
352
- * granted owner, like every other clamp here
353
- * (`canUsernameRunPrivilegedCommands()` returns true when `!isMultiUserMode()`),
354
- * and it returns the caller's own object untouched when there is nothing to strip.
355
- */
356
- async function clampEnvOverridesForOwner(owner, envOverrides) {
357
- if (!envOverrides)
358
- return envOverrides;
359
- const keys = ownerClampedEnvKeys();
360
- if (!keys.some((key) => key in envOverrides))
361
- return envOverrides;
362
- if (await canUsernameRunPrivilegedCommands(owner))
363
- return envOverrides;
364
- const clamped = { ...envOverrides };
365
- for (const key of keys)
366
- delete clamped[key];
367
- return clamped;
368
- }
369
305
  /** Test hook: the env-var half of the same multi-user safety gate. */
370
306
  export const _clampEnvOverridesForOwner = clampEnvOverridesForOwner;
371
307
  // ═══════════════════════════════════════════════════════════════