@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.
- package/README.md +14 -6
- package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
- package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
- package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
- package/assets/fonts/pocket-sans-world.manifest.json +49 -0
- package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
- package/assets/learning/NOTICE.md +33 -0
- package/assets/learning/jp-n3-ko.manifest.json +27 -0
- package/assets/learning/jp-n3-ko.pdl +0 -0
- package/dist/agent-cli-diagnostics.d.ts +20 -0
- package/dist/agent-cli-diagnostics.d.ts.map +1 -0
- package/dist/agent-cli-diagnostics.js +100 -0
- package/dist/agent-cli-diagnostics.js.map +1 -0
- package/dist/apme/collector.d.ts +18 -1
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +48 -3
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +275 -35
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +33 -5
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/index.d.ts.map +1 -1
- package/dist/apme/index.js +6 -2
- package/dist/apme/index.js.map +1 -1
- package/dist/apme/store.d.ts +41 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +153 -27
- package/dist/apme/store.js.map +1 -1
- package/dist/bridge-core.d.ts +15 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +30 -4
- package/dist/bridge-core.js.map +1 -1
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +55 -8
- package/dist/cli.js.map +1 -1
- package/dist/codex-rate-limits-live.d.ts +180 -10
- package/dist/codex-rate-limits-live.d.ts.map +1 -1
- package/dist/codex-rate-limits-live.js +475 -22
- package/dist/codex-rate-limits-live.js.map +1 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +263 -1
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +1 -0
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +25 -16
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/esp32-serial.d.ts +4 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +4 -0
- package/dist/esp32-serial.js.map +1 -1
- package/dist/font-pack.d.ts +15 -0
- package/dist/font-pack.d.ts.map +1 -0
- package/dist/font-pack.js +75 -0
- package/dist/font-pack.js.map +1 -0
- package/dist/glance-frame.d.ts.map +1 -1
- package/dist/glance-frame.js +2 -0
- package/dist/glance-frame.js.map +1 -1
- package/dist/http-auth-gate.d.ts.map +1 -1
- package/dist/http-auth-gate.js +9 -1
- package/dist/http-auth-gate.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/learning-pack.d.ts +24 -0
- package/dist/learning-pack.d.ts.map +1 -0
- package/dist/learning-pack.js +125 -0
- package/dist/learning-pack.js.map +1 -0
- package/dist/mdns.js +1 -1
- package/dist/mdns.js.map +1 -1
- package/dist/pairing-knocks.d.ts +80 -0
- package/dist/pairing-knocks.d.ts.map +1 -0
- package/dist/pairing-knocks.js +312 -0
- package/dist/pairing-knocks.js.map +1 -0
- package/dist/pixoo/micro-glyphs.d.ts +4 -0
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +29 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/pixoo-font.d.ts.map +1 -1
- package/dist/pixoo/pixoo-font.js +3 -1
- package/dist/pixoo/pixoo-font.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +17 -2
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pty-manager.d.ts +9 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +63 -4
- package/dist/pty-manager.js.map +1 -1
- package/dist/relayed-usage.d.ts +30 -0
- package/dist/relayed-usage.d.ts.map +1 -1
- package/dist/relayed-usage.js +25 -2
- package/dist/relayed-usage.js.map +1 -1
- package/dist/surface-protocol.d.ts.map +1 -1
- package/dist/surface-protocol.js +2 -0
- package/dist/surface-protocol.js.map +1 -1
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +8 -1
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/ws-server.d.ts +18 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +40 -2
- package/dist/ws-server.js.map +1 -1
- package/package.json +4 -3
- 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":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AA2H1E,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAiF,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon-server.d.ts","sourceRoot":"","sources":["../src/daemon-server.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon-server.js
CHANGED
|
@@ -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
|