@agentdeck/bridge 1.1.0 → 1.2.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 (107) hide show
  1. package/README.md +14 -6
  2. package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
  3. package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
  4. package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
  5. package/assets/fonts/pocket-sans-world.manifest.json +49 -0
  6. package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
  7. package/assets/learning/NOTICE.md +33 -0
  8. package/assets/learning/jp-n3-ko.manifest.json +27 -0
  9. package/assets/learning/jp-n3-ko.pdl +0 -0
  10. package/dist/agent-cli-diagnostics.d.ts +20 -0
  11. package/dist/agent-cli-diagnostics.d.ts.map +1 -0
  12. package/dist/agent-cli-diagnostics.js +100 -0
  13. package/dist/agent-cli-diagnostics.js.map +1 -0
  14. package/dist/apme/collector.d.ts +18 -1
  15. package/dist/apme/collector.d.ts.map +1 -1
  16. package/dist/apme/collector.js +48 -3
  17. package/dist/apme/collector.js.map +1 -1
  18. package/dist/apme/dashboard-html.d.ts.map +1 -1
  19. package/dist/apme/dashboard-html.js +275 -35
  20. package/dist/apme/dashboard-html.js.map +1 -1
  21. package/dist/apme/http.d.ts.map +1 -1
  22. package/dist/apme/http.js +33 -5
  23. package/dist/apme/http.js.map +1 -1
  24. package/dist/apme/index.d.ts.map +1 -1
  25. package/dist/apme/index.js +6 -2
  26. package/dist/apme/index.js.map +1 -1
  27. package/dist/apme/store.d.ts +41 -1
  28. package/dist/apme/store.d.ts.map +1 -1
  29. package/dist/apme/store.js +153 -27
  30. package/dist/apme/store.js.map +1 -1
  31. package/dist/bridge-core.d.ts +15 -0
  32. package/dist/bridge-core.d.ts.map +1 -1
  33. package/dist/bridge-core.js +30 -4
  34. package/dist/bridge-core.js.map +1 -1
  35. package/dist/cli.d.ts +11 -0
  36. package/dist/cli.d.ts.map +1 -1
  37. package/dist/cli.js +55 -8
  38. package/dist/cli.js.map +1 -1
  39. package/dist/codex-rate-limits-live.d.ts +180 -10
  40. package/dist/codex-rate-limits-live.d.ts.map +1 -1
  41. package/dist/codex-rate-limits-live.js +475 -22
  42. package/dist/codex-rate-limits-live.js.map +1 -1
  43. package/dist/daemon-server.d.ts.map +1 -1
  44. package/dist/daemon-server.js +263 -1
  45. package/dist/daemon-server.js.map +1 -1
  46. package/dist/daemon-ws-client.d.ts +1 -0
  47. package/dist/daemon-ws-client.d.ts.map +1 -1
  48. package/dist/daemon-ws-client.js +25 -16
  49. package/dist/daemon-ws-client.js.map +1 -1
  50. package/dist/daemon.js +1 -1
  51. package/dist/esp32-serial.d.ts +4 -0
  52. package/dist/esp32-serial.d.ts.map +1 -1
  53. package/dist/esp32-serial.js +4 -0
  54. package/dist/esp32-serial.js.map +1 -1
  55. package/dist/font-pack.d.ts +15 -0
  56. package/dist/font-pack.d.ts.map +1 -0
  57. package/dist/font-pack.js +75 -0
  58. package/dist/font-pack.js.map +1 -0
  59. package/dist/glance-frame.d.ts.map +1 -1
  60. package/dist/glance-frame.js +2 -0
  61. package/dist/glance-frame.js.map +1 -1
  62. package/dist/http-auth-gate.d.ts.map +1 -1
  63. package/dist/http-auth-gate.js +9 -1
  64. package/dist/http-auth-gate.js.map +1 -1
  65. package/dist/index.js +2 -2
  66. package/dist/index.js.map +1 -1
  67. package/dist/learning-pack.d.ts +24 -0
  68. package/dist/learning-pack.d.ts.map +1 -0
  69. package/dist/learning-pack.js +125 -0
  70. package/dist/learning-pack.js.map +1 -0
  71. package/dist/mdns.js +1 -1
  72. package/dist/mdns.js.map +1 -1
  73. package/dist/pairing-knocks.d.ts +80 -0
  74. package/dist/pairing-knocks.d.ts.map +1 -0
  75. package/dist/pairing-knocks.js +312 -0
  76. package/dist/pairing-knocks.js.map +1 -0
  77. package/dist/pixoo/micro-glyphs.d.ts +4 -0
  78. package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
  79. package/dist/pixoo/micro-glyphs.js +29 -0
  80. package/dist/pixoo/micro-glyphs.js.map +1 -1
  81. package/dist/pixoo/pixoo-font.d.ts.map +1 -1
  82. package/dist/pixoo/pixoo-font.js +3 -1
  83. package/dist/pixoo/pixoo-font.js.map +1 -1
  84. package/dist/pixoo/pixoo-renderer.d.ts +1 -1
  85. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  86. package/dist/pixoo/pixoo-renderer.js +17 -2
  87. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  88. package/dist/pty-manager.d.ts +9 -0
  89. package/dist/pty-manager.d.ts.map +1 -1
  90. package/dist/pty-manager.js +63 -4
  91. package/dist/pty-manager.js.map +1 -1
  92. package/dist/relayed-usage.d.ts +30 -0
  93. package/dist/relayed-usage.d.ts.map +1 -1
  94. package/dist/relayed-usage.js +25 -2
  95. package/dist/relayed-usage.js.map +1 -1
  96. package/dist/surface-protocol.d.ts.map +1 -1
  97. package/dist/surface-protocol.js +2 -0
  98. package/dist/surface-protocol.js.map +1 -1
  99. package/dist/tui/dashboard.d.ts.map +1 -1
  100. package/dist/tui/dashboard.js +8 -1
  101. package/dist/tui/dashboard.js.map +1 -1
  102. package/dist/ws-server.d.ts +18 -0
  103. package/dist/ws-server.d.ts.map +1 -1
  104. package/dist/ws-server.js +40 -2
  105. package/dist/ws-server.js.map +1 -1
  106. package/package.json +4 -3
  107. package/src/timebox/sync_ble.py +35 -16
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-server.d.ts","sourceRoot":"","sources":["../src/daemon-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"daemon-server.d.ts","sourceRoot":"","sources":["../src/daemon-server.ts"],"names":[],"mappings":"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"}
@@ -81,6 +81,7 @@ import { fetchUsageFromApi, resetConsecutiveFailures } from './usage-api.js';
81
81
  import { getOrCreateToken, isLocalConnection, validateToken } from './auth.js';
82
82
  import { buildPublicHealth, gateHttpRequest, isAuthorizedHttpRequest } from './http-auth-gate.js';
83
83
  import { closePairingWindow, getPairingWindowStatus, openPairingWindow, pairingWindowOpen, redeemPairingCode, } from './pairing-window.js';
84
+ import { approvePeer, dismissKnock, isApprovedPeer, pairingApprovals, pairingKnocks, recordKnock, revokePeer, } from './pairing-knocks.js';
84
85
  import { getLastFrame, renderPreviewFrame, onFrameRendered, offFrameRendered } from './pixoo/pixoo-bridge.js';
85
86
  import { loadIDotMatrixDevices } from './idotmatrix/idotmatrix-settings.js';
86
87
  import { handlePixooWake } from './pixoo/pixoo-client.js';
@@ -118,10 +119,12 @@ import { threadModule } from './card-modules.js';
118
119
  import { AutonomousPocketEngine, createAutonomousPocketModules, parsePocketAutonomyConfig, } from './pocket-autonomy.js';
119
120
  import { WeatherProvider, parseWeatherSettings } from './weather.js';
120
121
  import { CalendarProvider, parseCalendarSettings } from './calendar.js';
122
+ import { loadLearningPack, matchesLearningPackRequest } from './learning-pack.js';
123
+ import { loadFontPack, matchesFontPackRequest } from './font-pack.js';
121
124
  import { renderGlanceFrame, GLANCE_FRAME_BOARDS } from './glance-frame.js';
122
125
  import { isPortableReaderProfile, negotiateSurface, parseHttpSurfaceIdentity, pullOtaResponseStatus, surfaceErrorBody, surfaceAllowsEvent, SurfaceProtocolError, validateSurfaceOtaIdentity, validateSurfaceQueryTuple, usesPortableReaderProjection, } from './surface-protocol.js';
123
126
  import { resolveRelayedUsageEvent } from './relayed-usage.js';
124
- import { CARD_FEED_PATH, CARD_OUTBOX_PATH, GLANCE_FRAME_PATH } from '@agentdeck/shared';
127
+ import { CARD_FEED_PATH, CARD_OUTBOX_PATH, FONT_PACK_PATH, GLANCE_FRAME_PATH, LEARNING_PACK_PATH } from '@agentdeck/shared';
125
128
  import { readFileSync, statSync, writeFileSync, appendFileSync } from 'fs';
126
129
  import { readFile, rm } from 'fs/promises';
127
130
  import { tmpdir, networkInterfaces } from 'os';
@@ -517,6 +520,8 @@ function registerWifiEsp32(d, ws) {
517
520
  touchDownSamples: typeof d.touchDownSamples === 'number' ? d.touchDownSamples : undefined,
518
521
  touchGestures: typeof d.touchGestures === 'number' ? d.touchGestures : undefined,
519
522
  alsReady: typeof d.alsReady === 'boolean' ? d.alsReady : undefined,
523
+ repaintCount: typeof d.repaintCount === 'number' ? d.repaintCount : undefined,
524
+ fullRefreshCount: typeof d.fullRefreshCount === 'number' ? d.fullRefreshCount : undefined,
520
525
  lastSeenMs: Date.now(),
521
526
  });
522
527
  wifiEsp32Sockets.set(key, ws);
@@ -1066,6 +1071,8 @@ function buildNodeModuleHealth(startedModules) {
1066
1071
  sessionCount: status.sessionCount,
1067
1072
  usageFiveH: status.usageFiveH,
1068
1073
  processingCount: status.processingCount,
1074
+ repaintCount: status.repaintCount,
1075
+ fullRefreshCount: status.fullRefreshCount,
1069
1076
  deviceInfoFresh: status.deviceInfoFresh,
1070
1077
  } : null,
1071
1078
  lastReadAt: status.lastReadAt,
@@ -1429,6 +1436,29 @@ export async function startDaemon(opts) {
1429
1436
  // Assigned after BridgeCore exists. The HTTP handler reads it only after
1430
1437
  // startup; nullable keeps the tiny listen→core initialization window safe.
1431
1438
  let subagentTimeline = null;
1439
+ // The learning pack is immutable for one daemon lifetime. Package upgrades
1440
+ // arrive with a new AgentDeck build; validating once keeps every sleeping
1441
+ // reader pull cheap while a corrupt/mislicensed bundle fails closed.
1442
+ let surfaceLearningPack = null;
1443
+ try {
1444
+ surfaceLearningPack = loadLearningPack();
1445
+ log(`[agentdeck] Surface learning pack ready: ${surfaceLearningPack.advert.id}`
1446
+ + ` v${surfaceLearningPack.advert.version} (${surfaceLearningPack.advert.size} bytes,`
1447
+ + ` ${surfaceLearningPack.advert.licenseSpdx})`);
1448
+ }
1449
+ catch (err) {
1450
+ log(`[agentdeck] Surface learning pack disabled: ${err instanceof Error ? err.message : String(err)}`);
1451
+ }
1452
+ let surfaceFontPack = null;
1453
+ try {
1454
+ surfaceFontPack = loadFontPack();
1455
+ log(`[agentdeck] Surface font pack ready: ${surfaceFontPack.advert.id}`
1456
+ + ` v${surfaceFontPack.advert.version} (${surfaceFontPack.advert.size} bytes,`
1457
+ + ` ${surfaceFontPack.advert.licenseSpdx})`);
1458
+ }
1459
+ catch (err) {
1460
+ log(`[agentdeck] Surface font pack disabled: ${err instanceof Error ? err.message : String(err)}`);
1461
+ }
1432
1462
  // ===== HTTP server =====
1433
1463
  const httpServer = createServer((req, res) => {
1434
1464
  // LAN default-deny (issue #145): a request that is neither same-machine
@@ -1626,6 +1656,82 @@ export async function startDaemon(opts) {
1626
1656
  res.end(JSON.stringify({ open: false }));
1627
1657
  return;
1628
1658
  }
1659
+ // The operator-approval half of the credential path. A code asks the DEVICE
1660
+ // to prove something; these ask the OPERATOR, and only the second one a
1661
+ // camera-less reader can complete. Like the window routes above they sit
1662
+ // BEHIND the normal auth gate — a remote peer able to approve itself would
1663
+ // be granting itself a credential.
1664
+ //
1665
+ // GET /pair/knocks → { knocks, approvals }
1666
+ // POST /pair/approve { key } → { approved: true }
1667
+ // POST /pair/revoke { key } → { revoked: bool }
1668
+ // POST /pair/dismiss { key } → { dismissed: bool }
1669
+ // The wire shape is the Swift daemon's, field for field: the Mac app in
1670
+ // CLIENT mode calls these routes against whichever daemon holds 9120, so a
1671
+ // shape of our own would break its approval UI on exactly the machines that
1672
+ // run both. `deviceId` is "" rather than null when a row is address-scoped
1673
+ // (the client maps "" back to nil), and every timestamp is epoch ms.
1674
+ if (req.method === 'GET' && pathname === '/pair/knocks') {
1675
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
1676
+ res.end(JSON.stringify({
1677
+ knocks: pairingKnocks().map((k) => ({
1678
+ key: k.key,
1679
+ ip: k.ip,
1680
+ deviceId: k.deviceId ?? '',
1681
+ attempts: k.attempts,
1682
+ firstSeen: k.firstSeen,
1683
+ lastSeen: k.lastSeen,
1684
+ staleToken: k.staleToken,
1685
+ })),
1686
+ approved: pairingApprovals().map((a) => ({
1687
+ key: a.key,
1688
+ deviceId: a.deviceId ?? '',
1689
+ lastIP: a.lastIP,
1690
+ approvedAt: a.approvedAt,
1691
+ })),
1692
+ }));
1693
+ return;
1694
+ }
1695
+ if (req.method === 'POST' && (pathname === '/pair/approve'
1696
+ || pathname === '/pair/revoke' || pathname === '/pair/dismiss')) {
1697
+ void (async () => {
1698
+ let body = {};
1699
+ try {
1700
+ body = await readJsonBody(req, 4096);
1701
+ }
1702
+ catch { /* defaults */ }
1703
+ const key = typeof body.key === 'string' ? body.key : '';
1704
+ if (!key) {
1705
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1706
+ res.end(JSON.stringify({ error: 'key required' }));
1707
+ return;
1708
+ }
1709
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
1710
+ if (pathname === '/pair/approve') {
1711
+ approvePeer(key);
1712
+ log(`[agentdeck] Operator approved peer ${key}`);
1713
+ res.end(JSON.stringify({ approved: key }));
1714
+ }
1715
+ else if (pathname === '/pair/revoke') {
1716
+ revokePeer(key);
1717
+ log(`[agentdeck] Operator revoked peer ${key}`);
1718
+ res.end(JSON.stringify({ revoked: key }));
1719
+ }
1720
+ else {
1721
+ // Dismiss is silent, as in Swift — it is not an operator decision
1722
+ // about trust, just a row cleared off a list.
1723
+ dismissKnock(key);
1724
+ res.end(JSON.stringify({ dismissed: key }));
1725
+ }
1726
+ })().catch(() => {
1727
+ try {
1728
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1729
+ res.end(JSON.stringify({ error: 'could not update approvals' }));
1730
+ }
1731
+ catch { /* client gone */ }
1732
+ });
1733
+ return;
1734
+ }
1629
1735
  if (req.method === 'GET' && pathname === '/status') {
1630
1736
  const snap = core.stateMachine.getSnapshot();
1631
1737
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -2024,6 +2130,138 @@ export async function startDaemon(opts) {
2024
2130
  }
2025
2131
  return;
2026
2132
  }
2133
+ // ===== Licensed learning-pack delivery — portable-reader/v1 =====
2134
+ if (req.method === 'GET' && pathname === LEARNING_PACK_PATH) {
2135
+ const ip = req.socket.remoteAddress ?? '';
2136
+ let surfaceIdentity;
2137
+ try {
2138
+ surfaceIdentity = parseHttpSurfaceIdentity(req.headers, 'learning.pack.read');
2139
+ if (!surfaceIdentity) {
2140
+ throw new SurfaceProtocolError(400, 'surface_identity_required', 'Learning-pack delivery requires all Surface identity headers');
2141
+ }
2142
+ validateSurfaceQueryTuple(surfaceIdentity, parsedUrl.searchParams);
2143
+ if (surfaceIdentity.productId !== 'io.pocketdaily.reader') {
2144
+ throw new SurfaceProtocolError(409, 'surface_learning_product_mismatch', 'This learning pack is registered for Pocket Daily Reader');
2145
+ }
2146
+ }
2147
+ catch (err) {
2148
+ if (err instanceof SurfaceProtocolError) {
2149
+ res.writeHead(err.status, { 'Content-Type': 'application/json' });
2150
+ res.end(JSON.stringify(surfaceErrorBody(err)));
2151
+ return;
2152
+ }
2153
+ throw err;
2154
+ }
2155
+ if (!surfaceLearningPack) {
2156
+ res.writeHead(503, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
2157
+ res.end(JSON.stringify({ error: 'learning_pack_unavailable' }));
2158
+ return;
2159
+ }
2160
+ if (!matchesLearningPackRequest(surfaceLearningPack, parsedUrl.searchParams.get('id'), parsedUrl.searchParams.get('version'))) {
2161
+ res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
2162
+ res.end(JSON.stringify({ error: 'learning_pack_not_found' }));
2163
+ return;
2164
+ }
2165
+ const preferredIp = preferredPullOtaIp(ip, req.socket.localAddress ?? '');
2166
+ if (preferredIp) {
2167
+ const routePort = req.socket.localPort ?? 9120;
2168
+ log(`[agentdeck] Surface path redirect: learning-pack ${surfaceIdentity.board} (${ip}) via ${preferredIp}`);
2169
+ res.writeHead(307, {
2170
+ Location: `http://${preferredIp}:${routePort}${parsedUrl.pathname}${parsedUrl.search}`,
2171
+ 'Cache-Control': 'no-store',
2172
+ 'Connection': 'close',
2173
+ });
2174
+ res.end();
2175
+ return;
2176
+ }
2177
+ log(`[agentdeck] learning-pack download: ${surfaceIdentity.board} (${ip})`
2178
+ + ` ← ${surfaceLearningPack.advert.id} v${surfaceLearningPack.advert.version}`);
2179
+ const requestedFrom = Number(parsedUrl.searchParams.get('from') ?? '0');
2180
+ const requestedLimitRaw = parsedUrl.searchParams.get('limit');
2181
+ const requestedLimit = requestedLimitRaw == null ? undefined : Number(requestedLimitRaw);
2182
+ const { from, body } = pullOtaSegment(surfaceLearningPack.bytes, requestedFrom, requestedLimit);
2183
+ res.writeHead(200, {
2184
+ 'Content-Type': 'application/vnd.pocketdaily.learning-pack',
2185
+ 'Content-Length': String(body.length),
2186
+ ...(from > 0
2187
+ ? { 'Content-Range': body.length > 0
2188
+ ? `bytes ${from}-${from + body.length - 1}/${surfaceLearningPack.bytes.length}`
2189
+ : `bytes */${surfaceLearningPack.bytes.length}` }
2190
+ : {}),
2191
+ 'X-Learning-Pack-MD5': surfaceLearningPack.advert.md5,
2192
+ 'X-Learning-Pack-License': surfaceLearningPack.advert.licenseSpdx,
2193
+ 'Cache-Control': 'no-store',
2194
+ 'Connection': 'close',
2195
+ });
2196
+ res.end(body);
2197
+ return;
2198
+ }
2199
+ // ===== Licensed automatic font-pack delivery — portable-reader/v1 =====
2200
+ if (req.method === 'GET' && pathname === FONT_PACK_PATH) {
2201
+ const ip = req.socket.remoteAddress ?? '';
2202
+ let surfaceIdentity;
2203
+ try {
2204
+ surfaceIdentity = parseHttpSurfaceIdentity(req.headers, 'font.pack.read');
2205
+ if (!surfaceIdentity) {
2206
+ throw new SurfaceProtocolError(400, 'surface_identity_required', 'Font-pack delivery requires all Surface identity headers');
2207
+ }
2208
+ validateSurfaceQueryTuple(surfaceIdentity, parsedUrl.searchParams);
2209
+ if (surfaceIdentity.productId !== 'io.pocketdaily.reader') {
2210
+ throw new SurfaceProtocolError(409, 'surface_font_product_mismatch', 'This font pack is registered for Pocket Daily Reader');
2211
+ }
2212
+ }
2213
+ catch (err) {
2214
+ if (err instanceof SurfaceProtocolError) {
2215
+ res.writeHead(err.status, { 'Content-Type': 'application/json' });
2216
+ res.end(JSON.stringify(surfaceErrorBody(err)));
2217
+ return;
2218
+ }
2219
+ throw err;
2220
+ }
2221
+ if (!surfaceFontPack) {
2222
+ res.writeHead(503, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
2223
+ res.end(JSON.stringify({ error: 'font_pack_unavailable' }));
2224
+ return;
2225
+ }
2226
+ if (!matchesFontPackRequest(surfaceFontPack, parsedUrl.searchParams.get('id'), parsedUrl.searchParams.get('version'))) {
2227
+ res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
2228
+ res.end(JSON.stringify({ error: 'font_pack_not_found' }));
2229
+ return;
2230
+ }
2231
+ const preferredIp = preferredPullOtaIp(ip, req.socket.localAddress ?? '');
2232
+ if (preferredIp) {
2233
+ const routePort = req.socket.localPort ?? 9120;
2234
+ log(`[agentdeck] Surface path redirect: font-pack ${surfaceIdentity.board} (${ip}) via ${preferredIp}`);
2235
+ res.writeHead(307, {
2236
+ Location: `http://${preferredIp}:${routePort}${parsedUrl.pathname}${parsedUrl.search}`,
2237
+ 'Cache-Control': 'no-store',
2238
+ 'Connection': 'close',
2239
+ });
2240
+ res.end();
2241
+ return;
2242
+ }
2243
+ log(`[agentdeck] font-pack download: ${surfaceIdentity.board} (${ip})`
2244
+ + ` ← ${surfaceFontPack.advert.id} v${surfaceFontPack.advert.version}`);
2245
+ const requestedFrom = Number(parsedUrl.searchParams.get('from') ?? '0');
2246
+ const requestedLimitRaw = parsedUrl.searchParams.get('limit');
2247
+ const requestedLimit = requestedLimitRaw == null ? undefined : Number(requestedLimitRaw);
2248
+ const { from, body } = pullOtaSegment(surfaceFontPack.bytes, requestedFrom, requestedLimit);
2249
+ res.writeHead(200, {
2250
+ 'Content-Type': 'application/vnd.pocketdaily.cpfont',
2251
+ 'Content-Length': String(body.length),
2252
+ ...(from > 0
2253
+ ? { 'Content-Range': body.length > 0
2254
+ ? `bytes ${from}-${from + body.length - 1}/${surfaceFontPack.bytes.length}`
2255
+ : `bytes */${surfaceFontPack.bytes.length}` }
2256
+ : {}),
2257
+ 'X-Font-Pack-MD5': surfaceFontPack.advert.md5,
2258
+ 'X-Font-Pack-License': surfaceFontPack.advert.licenseSpdx,
2259
+ 'Cache-Control': 'no-store',
2260
+ 'Connection': 'close',
2261
+ });
2262
+ res.end(body);
2263
+ return;
2264
+ }
2027
2265
  // ===== Card Feed pull sync (M6) — wake-sync-sleep battery clients =====
2028
2266
  // Auth mirrors /apme: local connections are free; anything else needs the
2029
2267
  // pairing token (?token=). Devices hold it from provisioning — since
@@ -2193,6 +2431,19 @@ export async function startDaemon(opts) {
2193
2431
  const fwAdvert = stagedFwAdvert(pullBoard ?? undefined, otaIdentity, surfaceIdentity?.clientVersion);
2194
2432
  if (fwAdvert)
2195
2433
  feed.fw = fwAdvert;
2434
+ if (!fwAdvert && surfaceLearningPack
2435
+ && surfaceIdentity?.productId === 'io.pocketdaily.reader'
2436
+ && surfaceIdentity.capabilities.includes('learning.pack.update')) {
2437
+ // The advert deliberately rides unchanged responses. It is omitted
2438
+ // during OTA-first bootstrap so the device installs firmware before
2439
+ // spending its battery/link budget on content bytes.
2440
+ feed.learningPack = surfaceLearningPack.advert;
2441
+ }
2442
+ if (!fwAdvert && surfaceFontPack
2443
+ && surfaceIdentity?.productId === 'io.pocketdaily.reader'
2444
+ && surfaceIdentity.capabilities.includes('font.pack.update')) {
2445
+ feed.fontPack = surfaceFontPack.advert;
2446
+ }
2196
2447
  const telemetry = parsePullTelemetry(parsedUrl.searchParams);
2197
2448
  if (applyPullOtaBootstrap(feed, fwAdvert !== undefined)) {
2198
2449
  // Do not make a weak-link X3 receive and parse a full content deck
@@ -3610,6 +3861,7 @@ export async function startDaemon(opts) {
3610
3861
  core.wsServer.broadcast(resolveRelayedUsageEvent({
3611
3862
  relayed: u,
3612
3863
  ownCodexRateLimits: core.lastBuiltCodexRateLimits,
3864
+ ownLiveFamilyAuthorityExpiresAtMs: core.lastBuiltCodexLiveFamilyAuthorityExpiresAtMs,
3613
3865
  buildOwnUsage: () => core.buildUsage(),
3614
3866
  }));
3615
3867
  }
@@ -3663,6 +3915,8 @@ export async function startDaemon(opts) {
3663
3915
  version: d.version ?? null,
3664
3916
  stale: d.stale,
3665
3917
  serialActive: d.serialActive,
3918
+ repaintCount: d.repaintCount ?? null,
3919
+ fullRefreshCount: d.fullRefreshCount ?? null,
3666
3920
  })),
3667
3921
  };
3668
3922
  }
@@ -3730,6 +3984,14 @@ export async function startDaemon(opts) {
3730
3984
  bridgeIp: getLanIp(),
3731
3985
  bridgePort: port,
3732
3986
  }));
3987
+ // Operator-approval authentication (daemon only, for the same reason). The
3988
+ // roster belongs to the process that serves the operator's approval routes;
3989
+ // a session bridge sharing WsServer must not answer "is this peer approved?"
3990
+ // from a second, unowned copy of it.
3991
+ core.wsServer.setPairingApprovalHooks({
3992
+ isApproved: (ip, deviceId) => isApprovedPeer(ip, deviceId),
3993
+ record: (ip, deviceId, staleToken) => recordKnock(ip, deviceId, staleToken),
3994
+ });
3733
3995
  // WS-path display_state re-sync: the 5s serial heartbeat above only covers
3734
3996
  // USB-attached boards. WiFi boards (InkDeck) receive display_state edge-
3735
3997
  // triggered over the plugin WS — a missed wake edge would leave an e-ink