@gotcos/glasses-server 6.28.0 → 6.29.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/CHANGELOG.md CHANGED
@@ -2219,6 +2219,36 @@ unsaved capture, and makes batch status stop lying about finished work.
2219
2219
 
2220
2220
  # Changelog
2221
2221
 
2222
+ ## [6.29.0] - 2026-08-16
2223
+
2224
+ ### Continue is queued instead of holding the phone
2225
+
2226
+ - An attached turn is admitted with **202** and delivered in the background. A
2227
+ provider turn runs up to 21 minutes and iOS suspends the WebView the moment the
2228
+ phone is pocketed, so a synchronous turn was lost exactly when the user did the
2229
+ natural thing.
2230
+ - Every safety gate stays synchronous: body, replay, queued-prompt, lease, target,
2231
+ epoch, fence, claim, occupancy, head baseline, pin. A refusal still reaches the
2232
+ user immediately and precisely. Only the spawn moved.
2233
+ - **New:** `GET /api/agent-sessions/bindings/:bindingId/turns/:clientTurnId`
2234
+ serves the same durable ledger record the replay path serves, so a poll and a
2235
+ retry can never disagree about what a turn did. Reports `unknown` rather than
2236
+ `failed` for a key it has never seen.
2237
+ - Fixed: a post-202 refusal recorded nothing, so the status route would have
2238
+ answered `pending` forever. Pre-202 refusals keep the old semantics and stay
2239
+ re-evaluatable.
2240
+
2241
+ ### Fork, ungated
2242
+
2243
+ - `POST /api/agent-sessions/:provider/:threadId/fork` is registered whether or not
2244
+ `COS_THREAD_ATTACH_ENABLED` is set. Fork appends to nothing: the original is left
2245
+ byte-identical. It is what every refusal tells the user to do instead, and a
2246
+ refusal pointing at a route that 404s is a dead end.
2247
+
2248
+ **Required by COS Glasses 6.8.363.** Continue still needs
2249
+ `COS_THREAD_ATTACH_ENABLED=1`; with it unset the write routes are not registered
2250
+ and behavior is unchanged.
2251
+
2222
2252
  ## 6.12.7
2223
2253
 
2224
2254
  Pairs the public server with COS Glasses build 227+ meeting follow-ups while
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.28.0",
3
+ "version": "6.29.0",
4
4
  "description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.ts CHANGED
@@ -26,6 +26,7 @@ import { cosSpawnedPids } from './lib/agent-session-ownership-store.js'
26
26
  import { realOccupancyDirs, realOccupancyProbes } from './lib/occupancy-probes.js'
27
27
  import { realAttachedWorkspaceDeps, resolveAttachedWorkspace } from './lib/attached-workspace.js'
28
28
  import { deliverAttachedTurn, realAttachedTurnDeps } from './lib/attached-provider-adapter.js'
29
+ import { forkThread, realForkDeps } from './lib/fork-thread.js'
29
30
  import { nativeHead, realNativeHeadDeps } from './lib/native-head.js'
30
31
  import { threadOccupancy } from './lib/thread-occupancy.js'
31
32
  import { displayRouter } from './routes/display.js'
@@ -376,6 +377,43 @@ const deliverAttachedTurnForRoute = async (request: {
376
377
  })
377
378
  }
378
379
 
380
+ /**
381
+ * The shim for FORK — the action seventeen refusal strings in this feature already
382
+ * recommend, and which until 6.29 existed nowhere in the server or the app.
383
+ *
384
+ * Deliberately much thinner than the attached shim above, and every difference is
385
+ * load-bearing:
386
+ *
387
+ * - NO PREFLIGHT. `realAttachedTurnDeps` takes an occupancy re-check because it is
388
+ * about to APPEND to a thread someone may have open on their desktop. A fork
389
+ * appends to nothing: verified byte-for-byte on both providers on 2026-08-16,
390
+ * source sha256 unchanged across the run. A live desktop owner must therefore
391
+ * NOT block a fork — gating it here would refuse at exactly the moment fork is
392
+ * the only path the user has left.
393
+ * - NO `onSpawn` HAND-BACK. The turns route owns its own ledger record because it
394
+ * holds the `recordedPids` list that its `finally` releases from. The fork route
395
+ * holds no such list, so `fork-thread.ts` records and releases the child itself,
396
+ * exactly once, on every exit path — which keeps the recorded:released ratio the
397
+ * operator reads for leak detection at 1:1.
398
+ * - A WATERMARK IS SUPPLIED, and it is the point. `nativeHead` of the SOURCE, read
399
+ * once before the spawn and once after the child settles, is what turns "a fork
400
+ * does not touch the original" from a promise into a checked property. It is
401
+ * SYNCHRONOUS (native-head.ts:491), which is what lets the module read the
402
+ * baseline at the spawn boundary without an await. Unwired, the module can only
403
+ * ever report `unverified`, and it would report that honestly rather than
404
+ * claiming a verification it never performed.
405
+ */
406
+ const forkThreadForRoute = (request: {
407
+ provider: 'claude' | 'codex'
408
+ nativeThreadId: string
409
+ prompt: string
410
+ cwd: string
411
+ policy: 'read_only'
412
+ }): Promise<unknown> => forkThread({
413
+ ...request,
414
+ deps: realForkDeps((provider, threadId) => nativeHead(provider, threadId, nativeHeadDeps)),
415
+ })
416
+
379
417
  // API routes
380
418
  app.use('/api', healthRouter)
381
419
  app.use('/api', diagRouter)
@@ -413,6 +451,13 @@ app.use('/api', createAgentSessionBindingsRouter({
413
451
  },
414
452
  nativeHead: (provider, threadId) => nativeHead(provider, threadId, nativeHeadDeps),
415
453
  deliverAttachedTurn: deliverAttachedTurnForRoute as never,
454
+ forkThread: forkThreadForRoute,
455
+ // The fork's real spawn directory. Separate from `resolveTarget` above, which
456
+ // deliberately yields only fingerprints because plan 3.3 keeps a filesystem path
457
+ // off anything client-visible. Null refuses: never fall back to the server's own
458
+ // working directory, which is wherever the LaunchAgent happened to start.
459
+ resolveForkWorkspace: (provider, threadId) =>
460
+ resolveAttachedWorkspace(provider, threadId, attachedWorkspaceDeps)?.path ?? null,
416
461
  // One instance per process. The epoch high-water mark is only monotonic if a
417
462
  // single reader owns the durable store, so this must never be constructed twice.
418
463
  // `open()` never throws: an unreadable store yields a DEGRADED registry whose