@gotcos/glasses-server 6.18.4 → 6.18.6

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/.env.example CHANGED
@@ -53,9 +53,13 @@ BIND_HOST=0.0.0.0
53
53
  # COS_CODEX_MODEL=
54
54
  # COS_CODEX_REASONING_EFFORT=high # low | medium | high | xhigh | max | ultra
55
55
  #
56
- # Codex remains read-only by default. This is the only broader trust opt-in:
56
+ # Codex remains read-only by default. Broader trust opt-in (workdir writes +
57
+ # outbound network inside that sandbox — needed for Gmail API / HTTPS):
57
58
  # COS_CODEX_SANDBOX=workspace-write
58
- #
59
+ # Pair with ~/.codex/config.toml [sandbox_workspace_write] network_access = true
60
+ # for interactive Codex CLI; the managed glasses server also passes
61
+ # -c sandbox_workspace_write.network_access=true when workspace-write is set.
62
+
59
63
  # Claude preserves COS's established trusted-machine behavior by default.
60
64
  # Security-conscious installs can remove the permission bypass and restrict
61
65
  # Claude to COS's explicit per-query tool allowlist. Undeclared tools fail
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 6.18.6
2
+
3
+ - **Phone Restart works on COS Control managed installs.** Recovery status /
4
+ `/api/recovery/server/restart` previously required `COS_HARNESS=daemon`, but
5
+ Control's LaunchAgent sets `COS_MANAGED=1` with `COS_HARNESS=foreground`.
6
+ Health correctly advertised `managed: true` while the restart route returned
7
+ 409 "Server is not managed by the COS LaunchAgent". Gate now uses
8
+ `COS_MANAGED=1` (`isManagedRuntime()`), matching capabilities.
9
+
10
+ ## 6.18.5
11
+
12
+ - **Codex workspace-write now includes outbound network.** `workspace-write`
13
+ alone still blocked HTTPS (Gmail API, etc.) because Codex defaults
14
+ `sandbox_workspace_write.network_access` to off. When
15
+ `COS_CODEX_SANDBOX=workspace-write`, the managed server now passes
16
+ `-c sandbox_workspace_write.network_access=true` and the capability header
17
+ states HTTPS is available. Pair with the same key in `~/.codex/config.toml`
18
+ for interactive Codex CLI. Still not `danger-full-access` — writes stay
19
+ inside the workdir. Prefer COS `email_cache` / `email_gmail_api` over
20
+ approval-gated Google Workspace connector sends from glasses.
21
+
1
22
  ## 6.18.4
2
23
 
3
24
  - **Meeting sync progress on `/api/health`.** Post-meeting HQ polish writes
package/README.md CHANGED
@@ -70,7 +70,10 @@ without silently losing completed replies.
70
70
  > omit the execution mode default to Ask.
71
71
  > Existing `COS_CODEX_MODEL` / `COS_CODEX_REASONING_EFFORT` settings remain
72
72
  > supported on the migrated Frontier slot; leave them blank for auto-latest.
73
- > Codex runs **sandboxed read-only** by default (`COS_CODEX_SANDBOX` to adjust).
73
+ > Codex runs **sandboxed read-only** by default. Set
74
+ > `COS_CODEX_SANDBOX=workspace-write` for workdir writes + outbound network
75
+ > (`sandbox_workspace_write.network_access=true` is passed by the managed server
76
+ > and should also be set in `~/.codex/config.toml` for interactive Codex).
74
77
  > **Claude is the most permissive provider by default.** It runs with
75
78
  > `--dangerously-skip-permissions`, so a glasses query on the Claude/Opus path can
76
79
  > run shell commands and read, edit, and write files on this Mac without prompting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.18.4",
3
+ "version": "6.18.6",
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": {
@@ -83,14 +83,24 @@ const PHASE_LABELS: Record<Phase, string> = {
83
83
  // Sandbox policy for `codex exec`. This public server NEVER runs codex
84
84
  // unsandboxed — that would let a remote glasses query execute arbitrary commands
85
85
  // on the host. Default: read-only (safe for chat). COS_CODEX_SANDBOX=workspace-write
86
- // permits writes within the working directory only. Full host access is
86
+ // permits writes within the working directory only, and enables outbound network
87
+ // inside that sandbox (Gmail API, HTTPS fetches) via
88
+ // `sandbox_workspace_write.network_access=true`. Full host filesystem access is
87
89
  // intentionally not exposed by this server.
88
90
  function codexSandboxMode(): 'workspace-write' | 'read-only' {
89
91
  return process.env.COS_CODEX_SANDBOX === 'workspace-write' ? 'workspace-write' : 'read-only'
90
92
  }
91
93
 
92
94
  function codexSandboxArgs(): string[] {
93
- return ['--sandbox', codexSandboxMode(), '--skip-git-repo-check']
95
+ const args = ['--sandbox', codexSandboxMode(), '--skip-git-repo-check']
96
+ // workspace-write keeps the filesystem boundary but leaves network OFF by
97
+ // default in Codex. Glasses outbound Gmail (and similar HTTPS) needs this
98
+ // explicit opt-in — without it agents correctly report "cannot reach
99
+ // gmail.googleapis.com" even when workspace writes work.
100
+ if (codexSandboxMode() === 'workspace-write') {
101
+ args.push('-c', 'sandbox_workspace_write.network_access=true')
102
+ }
103
+ return args
94
104
  }
95
105
 
96
106
  /**
@@ -101,7 +111,7 @@ function codexSandboxArgs(): string[] {
101
111
  function codexCapabilityPrompt(): string {
102
112
  if (codexSandboxMode() === 'workspace-write') {
103
113
  return `TOOL CAPABILITY CONTRACT:
104
- You are an AGENT model running \`codex exec --sandbox workspace-write\`. Reads, searches, shell commands, and writes inside the working directory are available to you whether or not any list names them. Never claim a tool is unavailable based on that list or on any header — PROBE first with one real call; only an attempted call that actually failed is evidence a capability is missing. Writes outside the working directory are denied by the sandbox; say that plainly if you hit it.
114
+ You are an AGENT model running \`codex exec --sandbox workspace-write\` with \`sandbox_workspace_write.network_access=true\`. Reads, searches, shell commands, outbound HTTPS (including Gmail API / cos_python network calls), and writes inside the working directory are available to you whether or not any list names them. Never claim a tool is unavailable based on that list or on any header — PROBE first with one real call; only an attempted call that actually failed is evidence a capability is missing. Writes outside the working directory are denied by the sandbox; say that plainly if you hit it. Prefer the COS Gmail API script path (\`email_cache\` / \`email_gmail_api\` with confirm=True) over approval-gated Google Workspace connector sends.
105
115
  ${TOOL_HONESTY_CLAUSE}
106
116
  ${UNTRUSTED_CONTENT_CLAUSE}`
107
117
  }
@@ -21,9 +21,9 @@ export function managedRuntimeCapability(): ManagedRuntimeCapability {
21
21
  // Whisper lifecycle is private to the local controller. It is never
22
22
  // exposed as a network-reachable mutation capability.
23
23
  restartWhisper: false,
24
- // Server restart is performed by the trusted local helper through launchd,
25
- // never by an HTTP endpoint. This flag tells clients that managed recovery
26
- // exists without widening the network attack surface.
24
+ // Phone/companion may POST /api/recovery/server/restart when managed.
25
+ // launchd KeepAlive brings the process back after SIGTERM. Unmanaged
26
+ // foreground installs reject that route (restart_unmanaged).
27
27
  restartServer: managed,
28
28
  maintenanceDrain: managed,
29
29
  lifecycleProof: managed,
@@ -2,6 +2,7 @@ import { Router } from 'express'
2
2
  import { readFileSync } from 'node:fs'
3
3
  import { resolve } from 'node:path'
4
4
  import { atomicWriteFileSync } from '../lib/atomic-fs.js'
5
+ import { isManagedRuntime } from '../lib/managed-runtime.js'
5
6
  import { acquireMaintenance, getRecoveryActivityStatus } from '../lib/recovery-activity.js'
6
7
  import { getWhisperHealth, restartWhisperServer } from '../lib/whisper-local.js'
7
8
  import { serverMetrics } from '../lib/server-metrics.js'
@@ -19,14 +20,16 @@ recoveryRouter.get('/live', (_req, res) => {
19
20
  res.json({
20
21
  status: 'ok', bootId: serverMetrics.bootId, pid: process.pid,
21
22
  uptimeSeconds: Math.round((Date.now() - serverMetrics.startedAt) / 1000),
22
- managed: process.env.COS_HARNESS === 'daemon',
23
+ // COS Control LaunchAgent sets COS_MANAGED=1 with COS_HARNESS=foreground.
24
+ // Do not require COS_HARNESS=daemon — that false-negative blocked phone Restart.
25
+ managed: isManagedRuntime(),
23
26
  })
24
27
  })
25
28
 
26
29
  recoveryRouter.get('/recovery/status', (_req, res) => {
27
30
  res.json({
28
31
  bootId: serverMetrics.bootId,
29
- managed: process.env.COS_HARNESS === 'daemon',
32
+ managed: isManagedRuntime(),
30
33
  whisper: getWhisperHealth(),
31
34
  asr: { hqActive: false, hqQueued: 0, fastRestarting: false }, // public build: no HQ/fast ASR scheduler in this server
32
35
  activity: getRecoveryActivityStatus(),
@@ -46,7 +49,7 @@ recoveryRouter.post('/recovery/whisper/restart', async (_req, res) => {
46
49
  })
47
50
 
48
51
  recoveryRouter.post('/recovery/server/restart', (_req, res) => {
49
- if (process.env.COS_HARNESS !== 'daemon') {
52
+ if (!isManagedRuntime()) {
50
53
  return res.status(409).json({ error: 'Server is not managed by the COS LaunchAgent', reason: 'restart_unmanaged' })
51
54
  }
52
55
  const elapsed = Date.now() - lastRestartAt()