@openchambery/web 1.18.2-beta.11 → 1.18.2-beta.13

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 (51) hide show
  1. package/dist/assets/{AssistantView-BDxTxI4C.js → AssistantView-Bbqfj9CK.js} +1 -1
  2. package/dist/assets/{ChatContainer-C26Rw1h2.js → ChatContainer-hDTv6UXl.js} +7 -7
  3. package/dist/assets/{DiagramView-siSsyD6n.js → DiagramView-1hFDtyEy.js} +1 -1
  4. package/dist/assets/{MarkdownRendererImpl-Crm21KO-.js → MarkdownRendererImpl-5BLw0qYH.js} +1 -1
  5. package/dist/assets/{MobileDetailNavigation-m2VCvJuL.js → MobileDetailNavigation-DIyVGM50.js} +1 -1
  6. package/dist/assets/{MobileShareBridge-B4X9T8I9.js → MobileShareBridge-BQM-WOll.js} +1 -1
  7. package/dist/assets/{MobileSurface-DUhwRQFz.js → MobileSurface-KZ-kuSM-.js} +1 -1
  8. package/dist/assets/{MultiRunWindow-DHm99eU5.js → MultiRunWindow-DPOSSmY_.js} +1 -1
  9. package/dist/assets/{OverlayScrollbar-ivKSIGWh.js → OverlayScrollbar-C2pMNVMM.js} +1 -1
  10. package/dist/assets/{SettingsView-CpM0iBkp.js → SettingsView-DxL07Iba.js} +1 -1
  11. package/dist/assets/{SettingsWindow-DW1pl40e.js → SettingsWindow-DJ2o4zvh.js} +1 -1
  12. package/dist/assets/{TerminalView-z4ek-zEW.js → TerminalView-BB3tvloy.js} +1 -1
  13. package/dist/assets/{ToolOutputDialog-BZPEjn9H.js → ToolOutputDialog-D7DqChuM.js} +1 -1
  14. package/dist/assets/{appThemeRegistry-BUHyoCKY.js → appThemeRegistry-DhaoeTus.js} +2 -2
  15. package/dist/assets/{context-menu-uy-nsYLa.js → context-menu-D88vCGyB.js} +1 -1
  16. package/dist/assets/{gitApi-BukyCDHf.js → gitApi-DVNC4iYD.js} +1 -1
  17. package/dist/assets/{insertionBoundaries-Cn--FUpW.js → insertionBoundaries-hcIB6RSd.js} +1 -1
  18. package/dist/assets/{main-DBMzIIq8.js → main-CQrMAdMF.js} +4 -4
  19. package/dist/assets/{main-B9LVDYC3.js → main-D4aa2ZJz.js} +2 -2
  20. package/dist/assets/{miniChat-nHXgh499.js → miniChat-4N2-z2vf.js} +2 -2
  21. package/dist/assets/{mobile-DheffaWK.js → mobile-C-RRGcDW.js} +2 -2
  22. package/dist/assets/{multirun-BkftDz-w.js → multirun-Co8LbTjb.js} +1 -1
  23. package/dist/assets/{projectMeta-Cn8JzItU.js → projectMeta-D-ncdr5Z.js} +1 -1
  24. package/dist/assets/{radio-C9csxweA.js → radio-CD4P-ogh.js} +1 -1
  25. package/dist/assets/{renderElectronMiniChatApp-DLaYpuy5.js → renderElectronMiniChatApp-D5QcQTL0.js} +2 -2
  26. package/dist/assets/{renderMobileApp-CxDgYCxR.js → renderMobileApp-CnZZVbHU.js} +4 -4
  27. package/dist/assets/{runtimeEndpointReset-CCpti76q.js → runtimeEndpointReset-BQ5F5VZS.js} +2 -2
  28. package/dist/assets/{sortable-tabs-strip-sSkepr7C.js → sortable-tabs-strip-DQsv96ke.js} +1 -1
  29. package/dist/assets/{src-BcPciZGI.css → src-C8jn0x69.css} +1 -1
  30. package/dist/assets/{useAppFontEffects-BSolc5HW.js → useAppFontEffects-DygV6JSW.js} +2 -2
  31. package/dist/assets/{useDesktopWindowControlsLayout-CXQ-Ublz.js → useDesktopWindowControlsLayout-Cj63sNDg.js} +1 -1
  32. package/dist/assets/{useEffectiveDirectory-Dm7djJX8.js → useEffectiveDirectory-Dc904Fjg.js} +1 -1
  33. package/dist/assets/{useMobileNavigationStore-a9a3gOnd.js → useMobileNavigationStore-DNoNQA4y.js} +2 -2
  34. package/dist/assets/{useSessionAutoCleanup-CH6_9K8S.js → useSessionAutoCleanup-bBkcz_hu.js} +1 -1
  35. package/dist/assets/{useWorkerHighlightedLines-Qg2o83Ps.js → useWorkerHighlightedLines-mhBD0Do9.js} +1 -1
  36. package/dist/index.html +2 -2
  37. package/dist/mini-chat.html +2 -2
  38. package/dist/mobile.html +2 -2
  39. package/package.json +1 -1
  40. package/server/lib/event-stream/DOCUMENTATION.md +2 -2
  41. package/server/lib/event-stream/diff-summary.js +28 -19
  42. package/server/lib/event-stream/diff-summary.test.js +37 -10
  43. package/server/lib/event-stream/protocol.test.js +66 -3
  44. package/server/lib/opencode/DOCUMENTATION.md +1 -1
  45. package/server/lib/session-turn-pages/DOCUMENTATION.md +3 -3
  46. package/server/lib/session-turn-pages/reconcile.routes.test.js +6 -2
  47. package/server/lib/session-turn-pages/reconcile.service.test.js +6 -2
  48. package/server/lib/session-turn-pages/routes.js +3 -3
  49. package/server/lib/session-turn-pages/routes.test.js +41 -13
  50. package/server/lib/session-turn-pages/service.js +4 -2
  51. package/server/lib/session-turn-pages/service.test.js +13 -3
@@ -79,11 +79,11 @@ Every turn-page response summarizes `tool`, `reasoning`, and `file` parts plus m
79
79
 
80
80
  | Layer | Surface | Payload |
81
81
  |---|---|---|
82
- | **L1** | Turn pages, reconcile response pages, message/session SSE fan-out, exact `GET /api/session/:sessionID/message/:messageID` | Any `info.summary.diffs` **array** is projected to `summary.diffCount: number` + `summary.hasDiffs: boolean` and **`summary.diffs` is removed**. Other lightweight summary fields are preserved. `diffCount` is the original array length; `hasDiffs === diffCount > 0`. L1 never retains a file array. |
82
+ | **L1** | Turn pages, reconcile response pages, message/session SSE fan-out, exact `GET /api/session/:sessionID/message/:messageID` | Any `info.summary.diffs` **array** is projected to a **slim file list** `[{ file, status?, additions, deletions }]` plus additive markers `summary.diffCount: number` and `summary.hasDiffs: boolean`. Patch/before/after/from/to and other heavy bodies are stripped. Other lightweight summary fields are preserved. `diffCount` equals the slim array length; `hasDiffs === diffCount > 0`. Consumers may use either the slim array or the markers. |
83
83
  | **L2** | `GET /api/openchamber/sessions/:sessionID/changes?messageID=` (no `file`) | Host reads official `session.message` locally, returns only `{ files: [{ file, status?, additions, deletions }] }`. Never returns patch/before/after/from/to or the full message envelope. |
84
84
  | **L3** | Same route with `file=` | Host calls official `session.diff({ sessionID, messageID, directory })`, returns `{ diff: SnapshotFileDiff }` for the exact `file` string match (includes that file's patch). No match → HTTP 404 `{ error: 'change file not found', code: 'change_not_found' }`. |
85
85
 
86
- Exact message GET keeps the official `{ info, parts }` response shape and only applies L1 summary projection so materialize/recovery never relays a raw multi-megabyte `summary.diffs` array through the generic proxy. Abort/45s timeout semantics match the turn-page routes. Global `/api` auth still applies.
86
+ Exact message GET keeps the official `{ info, parts }` response shape and only applies L1 summary projection so materialize/recovery never relays raw multi-megabyte patch bodies through the generic proxy. Abort/45s timeout semantics match the turn-page routes. Global `/api` auth still applies.
87
87
 
88
88
  Changes query guards: `sessionID` / `messageID` length `1..512`; optional `file` length `1..4096` and rejects control characters / NUL. Failures: invalid params → 400, abort → 499, upstream/malformed → 502. Logs carry only safe flags and ids — never paths, patch bodies, or tokens.
89
89
 
@@ -100,7 +100,7 @@ Rules:
100
100
  - Slim tool input keeps only short locator and task-identity fields (path, pattern, query, command, `subagent_type`, `description`, skill `name` / `id`). Slim metadata keeps `sessionId`, skill `name`, plus edit `additions`/`deletions`. Projection still drops result bodies, task prompts, patches, and large write payloads.
101
101
  - Assistant `text` stays intact. User rows are no longer wholesale pass-through: their `file` parts are projected; other user parts stay as-is.
102
102
  - `file` projection keeps already-present size/width/height (top-level or `metadata`). `byteSize` is derived from a data URL when present. PNG/GIF/JPEG/WebP headers may fill missing `width`/`height`. Unknown dimensions omit those fields.
103
- - Message `info.summary.diffs` is L1-projected to `diffCount` / `hasDiffs` (file array removed). Reconcile preserves full parts and applies this same L1 summary projection to response-page byte budgeting and serialization; raw upstream scan budgets retain raw bytes. Clients that need file rows or patches use the Changes L2/L3 API.
103
+ - Message `info.summary.diffs` is L1-projected to a slim file list plus `diffCount` / `hasDiffs` (never patch bodies). Reconcile preserves full parts and applies this same L1 summary projection to response-page byte budgeting and serialization; raw upstream scan budgets retain raw bytes. Clients that need a dedicated file-list envelope or per-file patches use the Changes L2/L3 API.
104
104
  - A slim `file` part is a reference, not renderable content. Clients must hydrate the full message (`session.message` by `messageID`) before painting or editing the attachment. Do not treat slim file metadata as a complete part.
105
105
  - Records that gain nothing from projection keep their original object reference, so identity-based change detection downstream still works.
106
106
  - Projection applies to every turn-page response, including first packet, prepend, and explicit cursors (`before` present). Reconcile routes return full parts, unchanged.
@@ -244,17 +244,21 @@ describe('registerSessionTurnPageRoutes — reconcile', () => {
244
244
 
245
245
  expect(res.statusCode).toBe(200);
246
246
  expect(res.body.records[0].info.summary).toEqual({
247
+ diffs: [{
248
+ file: 'src/large.ts',
249
+ status: 'modified',
250
+ additions: 5,
251
+ deletions: 1,
252
+ }],
247
253
  diffCount: 1,
248
254
  hasDiffs: true,
249
255
  });
250
- expect(res.body.records[0].info.summary.diffs).toBeUndefined();
251
256
  expect(res.body.records[0].parts[0]).toEqual({
252
257
  id: 'prt_1',
253
258
  type: 'text',
254
259
  text: 'full recovery text',
255
260
  });
256
261
  expect(JSON.stringify(res.body)).not.toContain(patch);
257
- expect(JSON.stringify(res.body)).not.toContain('src/large.ts');
258
262
  });
259
263
 
260
264
  it('returns HTTP 200 for resetRequired (anchor lost / budget rebuild)', async () => {
@@ -512,13 +512,17 @@ describe('createSessionReconcileService', () => {
512
512
 
513
513
  expect(result.ok).toBe(true);
514
514
  expect(result.records[0].info.summary).toEqual({
515
+ diffs: [{
516
+ file: 'src/large.ts',
517
+ status: 'modified',
518
+ additions: 7,
519
+ deletions: 2,
520
+ }],
515
521
  diffCount: 1,
516
522
  hasDiffs: true,
517
523
  });
518
- expect(result.records[0].info.summary.diffs).toBeUndefined();
519
524
  expect(result.responseBytes).toBeLessThanOrEqual(1024);
520
525
  expect(JSON.stringify(result.records)).not.toContain(patch);
521
- expect(JSON.stringify(result.records)).not.toContain('src/large.ts');
522
526
  });
523
527
 
524
528
  it('rejects continuation that does not bind runtime/directory/session/anchor/head', async () => {
@@ -307,7 +307,7 @@ const createSdkFetchDiff = ({ buildOpenCodeUrl, getOpenCodeAuthHeaders, logger }
307
307
 
308
308
  /**
309
309
  * Project exact message GET payload: keep original `{ info, parts }` shape,
310
- * only L1-project `info.summary.diffs` → diffCount/hasDiffs.
310
+ * only L1-project `info.summary.diffs` → slim file list + diffCount/hasDiffs.
311
311
  */
312
312
  export const projectExactMessagePayload = (payload) => {
313
313
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
@@ -625,8 +625,8 @@ export const registerSessionTurnPageRoutes = (app, dependencies = {}) => {
625
625
 
626
626
  /**
627
627
  * Exact message GET — registered before generic `/api` proxy so materialize /
628
- * recovery never relay a raw 14MB `summary.diffs` array. Returns the official
629
- * `{ info, parts }` shape with L1 summary projection only.
628
+ * recovery never relay raw multi-megabyte patch bodies in `summary.diffs`.
629
+ * Returns the official `{ info, parts }` shape with L1 slim-list projection.
630
630
  */
631
631
  app.get('/api/session/:sessionID/message/:messageID', async (req, res) => {
632
632
  const sessionID = req.params?.sessionID;
@@ -320,7 +320,7 @@ describe('registerSessionTurnPageRoutes', () => {
320
320
  expect(part.state.output).toBeUndefined();
321
321
  });
322
322
 
323
- it('projects message summary.diffs to L1 count/marker before serializing a turn-page response', async () => {
323
+ it('projects message summary.diffs to L1 slim list + markers before serializing a turn-page response', async () => {
324
324
  const patch = 'diff-body-'.repeat(20_000);
325
325
  const loadPage = vi.fn(async () => ({
326
326
  ok: true,
@@ -335,6 +335,8 @@ describe('registerSessionTurnPageRoutes', () => {
335
335
  additions: 9,
336
336
  deletions: 3,
337
337
  patch,
338
+ before: 'old',
339
+ after: 'new',
338
340
  }],
339
341
  },
340
342
  },
@@ -356,15 +358,21 @@ describe('registerSessionTurnPageRoutes', () => {
356
358
 
357
359
  expect(res.statusCode).toBe(200);
358
360
  expect(res.body.records[0].info.summary).toEqual({
361
+ diffs: [{
362
+ file: 'src/large.ts',
363
+ status: 'modified',
364
+ additions: 9,
365
+ deletions: 3,
366
+ }],
359
367
  diffCount: 1,
360
368
  hasDiffs: true,
361
369
  });
362
- expect(res.body.records[0].info.summary.diffs).toBeUndefined();
363
370
  expect(JSON.stringify(res.body)).not.toContain(patch);
364
- expect(JSON.stringify(res.body)).not.toContain('src/large.ts');
371
+ expect(JSON.stringify(res.body)).not.toContain('"before"');
372
+ expect(JSON.stringify(res.body)).not.toContain('"after"');
365
373
  });
366
374
 
367
- it('keeps L1 turn-page serialization under 64KiB for a 463-file / ~14MB patch summary', async () => {
375
+ it('keeps L1 turn-page serialization under 256KiB for a 463-file / ~14MB patch summary', async () => {
368
376
  const patchBody = 'P'.repeat(Math.floor((14 * 1024 * 1024) / 463));
369
377
  const diffs = Array.from({ length: 463 }, (_, i) => ({
370
378
  file: `src/generated/file-${i}.ts`,
@@ -374,6 +382,8 @@ describe('registerSessionTurnPageRoutes', () => {
374
382
  patch: `${patchBody}-${i}`,
375
383
  before: `before-${i}`,
376
384
  after: `after-${i}`,
385
+ from: `from-${i}`,
386
+ to: `to-${i}`,
377
387
  }));
378
388
  const loadPage = vi.fn(async () => ({
379
389
  ok: true,
@@ -401,16 +411,29 @@ describe('registerSessionTurnPageRoutes', () => {
401
411
 
402
412
  expect(res.statusCode).toBe(200);
403
413
  const serialized = JSON.stringify(res.body);
404
- expect(res.body.records[0].info.summary).toEqual({
405
- title: 'huge',
406
- diffCount: 463,
407
- hasDiffs: true,
414
+ const summary = res.body.records[0].info.summary;
415
+ expect(summary.diffCount).toBe(463);
416
+ expect(summary.hasDiffs).toBe(true);
417
+ expect(summary.diffs).toHaveLength(463);
418
+ expect(summary.diffs[0]).toEqual({
419
+ file: 'src/generated/file-0.ts',
420
+ status: 'modified',
421
+ additions: 0,
422
+ deletions: 0,
423
+ });
424
+ expect(summary.diffs[462]).toEqual({
425
+ file: 'src/generated/file-462.ts',
426
+ status: 'modified',
427
+ additions: 462 % 7,
428
+ deletions: 462 % 3,
408
429
  });
409
- expect(res.body.records[0].info.summary.diffs).toBeUndefined();
410
- expect(serialized).not.toContain('"diffs"');
411
430
  expect(serialized).not.toContain(patchBody.slice(0, 64));
412
- expect(serialized).not.toContain('src/generated/file-0.ts');
413
- expect(Buffer.byteLength(serialized, 'utf8')).toBeLessThan(64 * 1024);
431
+ expect(serialized).not.toContain('"patch"');
432
+ expect(serialized).not.toContain('"before"');
433
+ expect(serialized).not.toContain('"after"');
434
+ expect(serialized).not.toContain('"from"');
435
+ expect(serialized).not.toContain('"to"');
436
+ expect(Buffer.byteLength(serialized, 'utf8')).toBeLessThan(256 * 1024);
414
437
  });
415
438
 
416
439
  it('projects file parts on first packet and prepend the same way', async () => {
@@ -630,6 +653,12 @@ describe('registerSessionTurnPageRoutes — exact message GET', () => {
630
653
  role: 'user',
631
654
  summary: {
632
655
  title: 'turn',
656
+ diffs: [{
657
+ file: 'a.ts',
658
+ status: 'modified',
659
+ additions: 2,
660
+ deletions: 1,
661
+ }],
633
662
  diffCount: 1,
634
663
  hasDiffs: true,
635
664
  },
@@ -637,7 +666,6 @@ describe('registerSessionTurnPageRoutes — exact message GET', () => {
637
666
  parts: [{ id: 'prt_1', type: 'text', text: 'hello' }],
638
667
  });
639
668
  expect(JSON.stringify(res.body)).not.toContain(patch);
640
- expect(JSON.stringify(res.body)).not.toContain('a.ts');
641
669
  });
642
670
 
643
671
  it('rejects oversize messageID / sessionID with 400', async () => {
@@ -459,8 +459,10 @@ const isUserRecord = (record) => {
459
459
  };
460
460
 
461
461
  /**
462
- * L1: project message `summary.diffs` arrays to `{ diffCount, hasDiffs }` and
463
- * remove the file array. Preserves every other message / summary field.
462
+ * L1: project message `summary.diffs` to a slim file list
463
+ * `{ file, status?, additions, deletions }` plus additive `diffCount` /
464
+ * `hasDiffs`. Never retains patch/before/after/from/to. Preserves every other
465
+ * message / summary field.
464
466
  */
465
467
  export const projectMessageDiffSummaries = (records) => {
466
468
  if (!Array.isArray(records) || records.length === 0) return records;
@@ -172,7 +172,7 @@ describe('projectSlimParts', () => {
172
172
  },
173
173
  });
174
174
 
175
- it('projects message summary.diffs to L1 count/marker even when parts are empty', () => {
175
+ it('projects message summary.diffs to L1 slim list + markers even when parts are empty', () => {
176
176
  const patch = 'diff-body-'.repeat(20_000);
177
177
  const [projected] = projectSlimParts([{
178
178
  info: {
@@ -186,6 +186,10 @@ describe('projectSlimParts', () => {
186
186
  additions: 12,
187
187
  deletions: 4,
188
188
  patch,
189
+ before: 'old-body',
190
+ after: 'new-body',
191
+ from: 'from-blob',
192
+ to: 'to-blob',
189
193
  }],
190
194
  },
191
195
  },
@@ -194,12 +198,18 @@ describe('projectSlimParts', () => {
194
198
 
195
199
  expect(projected.info.summary).toEqual({
196
200
  title: 'kept',
201
+ diffs: [{
202
+ file: 'src/large.ts',
203
+ status: 'modified',
204
+ additions: 12,
205
+ deletions: 4,
206
+ }],
197
207
  diffCount: 1,
198
208
  hasDiffs: true,
199
209
  });
200
- expect(projected.info.summary.diffs).toBeUndefined();
201
210
  expect(JSON.stringify(projected)).not.toContain(patch);
202
- expect(JSON.stringify(projected)).not.toContain('src/large.ts');
211
+ expect(JSON.stringify(projected)).not.toContain('old-body');
212
+ expect(JSON.stringify(projected)).not.toContain('from-blob');
203
213
  });
204
214
 
205
215
  it('drops tool output and metadata but keeps identity and status', () => {