@gotcos/glasses-server 7.0.31 → 7.0.34

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.
@@ -123,7 +123,8 @@
123
123
  return W.api('/api/web-bridge/memories', { method: 'POST', body: { id: ++seq, op: op, args: a }, timeoutMs: limit }).then(function (r) {
124
124
  if (r && r.ok) return r.details || {};
125
125
  var raw = (r && r.message) || 'Your Mac did not answer that.';
126
- var err = new Error(PLAIN[raw] || raw); err.code = raw; throw err;
126
+ // 7.0.33 A8: a code this pane has no words for goes through the page's plain words, never on screen as is.
127
+ var err = new Error(PLAIN[raw] || (W.plainMessage ? W.plainMessage(raw) : raw)); err.code = raw; throw err;
127
128
  });
128
129
  }
129
130
  // A request that never reached COS is said in plain words by W.api itself (app.js, 6.99.30: code 'unreachable'),
@@ -217,7 +218,7 @@
217
218
  seedDecisions(r.rows); prefetchLessons(r.rows);
218
219
  }, function (e) {
219
220
  if (serial !== serials.recent) return;
220
- if (more) W.toast(e.message); else { L.error = e.message; L.loaded = true; }
221
+ if (more) W.toast(e.message); else { L.error = e.message; L.errorCode = e.code || ''; L.loaded = true; }
221
222
  }).then(function () { if (serial === serials.recent) L.loading = false; paint(); });
222
223
  }
223
224
  function loadReview(more) {
@@ -239,7 +240,7 @@
239
240
  seedDecisions(r.rows);
240
241
  }, function (e) {
241
242
  if (serial !== serials.review) return;
242
- if (more) W.toast(e.message + ' Refresh To review to start again.'); else { L.error = e.message; L.loaded = true; }
243
+ if (more) W.toast(e.message + ' Refresh To review to start again.'); else { L.error = e.message; L.errorCode = e.code || ''; L.loaded = true; }
243
244
  }).then(function () { if (serial === serials.review) L.loading = false; paint(); });
244
245
  }
245
246
  var memTimer = null;
@@ -267,7 +268,7 @@
267
268
  L.loaded = true; L.error = '';
268
269
  }, function (e) {
269
270
  if (serial !== serials.mem) return;
270
- if (more) W.toast(e.message); else { L.error = e.message; L.loaded = true; }
271
+ if (more) W.toast(e.message); else { L.error = e.message; L.errorCode = e.code || ''; L.loaded = true; }
271
272
  }).then(function () { if (serial === serials.mem) L.loading = false; paint(); });
272
273
  }
273
274
  function memQuery(q) {
@@ -777,7 +778,8 @@
777
778
  if (n) parts.push('<span><b>' + fmt(n) + '</b> ' + plural(n, 'change', 'changes') + ' to review</span>');
778
779
  if (S.recent.total != null) parts.push('<span><b>' + fmt(S.recent.total) + '</b> ' + plural(S.recent.total, 'event', 'events') + ' in 90 days</span>');
779
780
  else if (S.recent.loading) parts.push('<span>Counting events…</span>');
780
- else if (S.recent.error) parts.push('<span class="mm-warn">Events in 90 days could not be counted</span>');
781
+ // 7.0.33 A8: said once. When the Recent learning list below already says it failed, this line does not say it again.
782
+ else if (S.recent.error && S.recent.rows.length) parts.push('<span class="mm-warn">Events in 90 days could not be counted</span>');
781
783
  if (S.recent.loaded) parts.push('<span><b>' + fmt(checked) + '</b> ' + plural(checked, 'result', 'results') + ' checked on this page</span>');
782
784
  parts.push('<span>Capture status in ' + textBtn('settings', 'Memory settings') + '</span>');
783
785
  parts.push('<span>' + textBtn('log', 'View activity log') + '<span class="mm-phone-only"><span class="mm-dot" aria-hidden="true"> · </span>' + textBtn('refresh', 'Refresh') + '</span></span>');
@@ -816,6 +818,12 @@
816
818
  }
817
819
  function appliedExcerpt(e) { return ((e.source_refs || [])[0] || {}).excerpt || (e.detail && e.detail.excerpts && e.detail.excerpts[0]) || ''; }
818
820
  function groupHead(text, side) { return '<h2 class="group-title mm-group"><span>' + text + '</span>' + (side ? '<span>' + side + '</span>' : '') + '</h2>'; }
821
+ // 7.0.33 A8: a list that failed because its part of COS is not set up on this Mac says that, once, with no Try again
822
+ // (trying again cannot help); any other failure keeps its own title and Try again.
823
+ function failBox(L, title, retry) {
824
+ if (W.notSetUp && W.notSetUp(L.errorCode)) return stateBox('Not set up on this Mac.', L.error, null);
825
+ return stateBox(title, L.error, retry);
826
+ }
819
827
  function stateBox(title, body, retry) {
820
828
  return '<div class="cx-list"><div class="mm-state"><strong>' + esc(title) + '</strong>' + (body ? '<span>' + esc(body) + '</span>' : '') +
821
829
  (retry ? '<span class="mm-state-act">' + btn(retry, 'Try again') + '</span>' : '') + '</div></div>';
@@ -841,7 +849,7 @@
841
849
  function recentListHtml() {
842
850
  var L = S.recent, sel = selectedId();
843
851
  if (!L.loaded && L.loading) return groupHead('Recent learning', '90 days') + loadingBox('Reading what COS learned in the last 90 days');
844
- if (L.error && !L.rows.length) return groupHead('Recent learning', '90 days') + stateBox('Recent learning did not load.', L.error, 'refresh');
852
+ if (L.error && !L.rows.length) return groupHead('Recent learning', '90 days') + failBox(L, 'Recent learning did not load.', 'refresh');
845
853
  if (!L.rows.length) return groupHead('Recent learning', '90 days') + stateBox('Nothing learned in the last 90 days.', 'Lessons, corrections and recalls show here as COS records them.');
846
854
  return groupHead('Recent learning', L.total != null ? fmt(L.total) + ' in 90 days' : '90 days') +
847
855
  '<div class="cx-list">' + L.rows.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>' +
@@ -859,7 +867,7 @@
859
867
  '<p class="mm-group-note">Suggested rules still gathering evidence. They cannot become a rule until their confidence reaches the bar, and move to Needs you now on their own when it does.</p>' +
860
868
  '<div class="cx-list">' + learning.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>' : '';
861
869
  if (!L.loaded && L.loading) return out + groupHead('Proposed changes') + loadingBox('Reading the changes waiting for you') + learnHtml;
862
- if (L.error && !L.rows.length) return out + groupHead('Proposed changes') + stateBox('The review list did not load.', L.error, 'refresh') + learnHtml;
870
+ if (L.error && !L.rows.length) return out + groupHead('Proposed changes') + failBox(L, 'The review list did not load.', 'refresh') + learnHtml;
863
871
  if (!needs.length && !rest.length) return groupHead('To review') + stateBox('You are up to date.', 'Nothing is waiting for your decision.') + learnHtml;
864
872
  if (other.length) out += groupHead('Proposed changes', fmt(other.length) + ' open') + '<div class="cx-list">' + other.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>';
865
873
  if (ready.length) {
@@ -881,7 +889,7 @@
881
889
  var L = S.mem, q = L.q.trim(), rows = L.hits != null ? L.hits : L.rows, sel = selectedId();
882
890
  var head = groupHead(q ? 'Memories matching “' + esc(q) + '”' : 'Saved memories', !q && L.total != null ? fmt(L.total) + ' stored' : '');
883
891
  if (!L.loaded && L.loading) return head + loadingBox(q ? 'Searching your memories' : 'Reading your saved memories');
884
- if (L.error && !rows.length) return head + stateBox(q ? 'That search did not finish.' : 'Your memories did not load.', L.error, 'mem-retry');
892
+ if (L.error && !rows.length) return head + failBox(L, q ? 'That search did not finish.' : 'Your memories did not load.', 'mem-retry');
885
893
  if (!rows.length) return head + stateBox(q ? 'No memories match that search.' : 'No memories yet.', q ? 'Try fewer or different words.' : 'COS saves memories as you work. Plain notes in the memory folder show here too.');
886
894
  var cursor = q ? L.hitCursor : L.cursor;
887
895
  return head + '<div class="cx-list">' + rows.map(function (m) { return memRowHtml(m, m.id === sel); }).join('') + '</div>' +
@@ -1864,7 +1872,8 @@
1864
1872
  function stopWatches() { clearInterval(watch.ingest); watch.ingest = null; clearInterval(watch.fetch); watch.fetch = null; clearInterval(watch.build); watch.build = null; clearInterval(askTimer); }
1865
1873
 
1866
1874
  // ── decisions (writes, each from a tap) ──────────────────
1867
- function arm(key) { S.armed = key; paint(); setTimeout(function () { if (S.armed === key) { S.armed = ''; paint(); } }, 6000); }
1875
+ // 7.0.33 (Q1): the one expiry every armed button uses (app.js ARM_EXPIRE_MS, 5 s; this was 6 s).
1876
+ function arm(key) { S.armed = key; paint(); setTimeout(function () { if (S.armed === key) { S.armed = ''; paint(); } }, W.armExpireMs || 5000); }
1868
1877
  function afterWrite() { loadStatus(); }
1869
1878
  function reviewMemory(id, decision) {
1870
1879
  if (S.reviewBusy) return;
@@ -468,15 +468,27 @@
468
468
  }).join('') + '</div>';
469
469
  }
470
470
  function turnCards(t, msgKey) {
471
- var a = atts(t);
471
+ // 7.0.33 A2 (Anand round 2, slide 10): a small time under each message, when he asked and when COS answered. The clock
472
+ // alone when it is the answer's day (the header has the date); the date too when the question was on an earlier one.
473
+ // A server before 7.0.33 sends no question time: then only the answer has one.
474
+ function saidTimes(t) {
475
+ var asked = t.queryTimestamp != null ? toDate(t.queryTimestamp) : null, answered = t.timestamp != null ? toDate(t.timestamp) : null;
476
+ var tag = function (d, text) { return '<time datetime="' + esc(d.toISOString()) + '">' + esc(text) + '</time>'; };
477
+ return {
478
+ asked: asked ? tag(asked, answered && sameDay(asked, answered) ? shortTime(asked) : timeLabel(asked)) : '',
479
+ answered: answered ? tag(answered, shortTime(answered)) : ''
480
+ };
481
+ }
482
+ var a = atts(t), when = saidTimes(t);
472
483
  // 7.0.30, Miles's pick A: who said what by side, in the COS chat's own shapes. His words on the right in the chat's
473
484
  // gold bubble with "You" under it; COS on the left beside its mark, on the page. A screen reader hears "You said"
474
485
  // and "COS said" first, since the side and the mark are visual only.
475
486
  return '<div class="cx-said you"><span class="sr-only">You said</span><div class="cx-said-bubble cx-card-body" data-hl>' + esc(String(t.query || '').trim() || '(empty query)').replace(/\n/g, '<br>') + '</div>' +
476
- '<span class="cx-said-who" aria-hidden="true">You</span></div>' +
487
+ (when.asked ? '<span class="cx-said-who"><span aria-hidden="true">You · </span>' + when.asked + '</span>' : '<span class="cx-said-who" aria-hidden="true">You</span>') + '</div>' +
477
488
  stripHtml(a.filter(isUserAtt), true, msgKey) +
478
489
  '<div class="cx-said cos"><span class="cc-avatar" aria-hidden="true"><svg class="icon"><use href="#i-mark"/></svg></span><div class="cx-said-text"><span class="sr-only">COS said</span>' +
479
- '<div class="cx-card-body md" data-hl>' + W.md(W.structureLensAnswer(String(t.text || ''))) + '</div></div></div>' +
490
+ '<div class="cx-card-body md" data-hl>' + W.md(W.structureLensAnswer(String(t.text || ''))) + '</div>' +
491
+ (when.answered ? '<span class="cx-said-who cx-said-when">' + when.answered + '</span>' : '') + '</div></div>' +
480
492
  stripHtml(a.filter(function (x) { return !isUserAtt(x); }), false, msgKey);
481
493
  }
482
494
  function copyTurnText(t) {
@@ -109,7 +109,12 @@
109
109
  var left = Math.ceil((deadline - Date.now()) / 1000);
110
110
  return Math.max(0, Math.min(span, isFinite(left) ? left : 0));
111
111
  }
112
- function timeLeft(s) { return s > 99 ? Math.ceil(s / 60) + ' min left' : s + ' s left'; }
112
+ // 7.0.32 (X1 ask 1): a question can wait 8 hours, so from one hour the time left reads hours and minutes ("7 h 59 m
113
+ // left", never "480 min left"). From an hour on it is rounded down; under an hour it keeps 7.0.31's minutes (rounded up).
114
+ function timeLeft(s) {
115
+ if (s >= 3600) { var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60); return h + ' h' + (m ? ' ' + m + ' m' : '') + ' left'; }
116
+ return s > 99 ? Math.ceil(s / 60) + ' min left' : s + ' s left';
117
+ }
113
118
  function engine(item) { return item.provider === 'codex' ? 'Codex' : 'Claude'; }
114
119
 
115
120
  // ── the capability, then the poll ─────────────────────
@@ -24,7 +24,7 @@
24
24
  // 6.99.11 (Drop it P2): shown only when the server has the P2 settings and Drop it is on.
25
25
  { id: 'reach', title: 'COS reaching out', summary: 'What COS tells you about on its own, when it may interrupt, and your reminders.', mac: [], p2: true },
26
26
  { id: 'privacy', title: 'Privacy and permissions', summary: 'What COS can use on this Mac. Each permission turns on one feature.', mac: ['Permissions'] },
27
- { id: 'pet', title: 'Pet', summary: 'Which pet this browser shows on Today, and whether it shows. Size, motion and the Mac\u2019s own pet are set in COS Control.', mac: ['Session pet size and motion', 'Characters on the Mac'] },
27
+ { id: 'pet', title: 'Pet', summary: 'Which pet this browser shows, and where: on Today, in the top bar on a phone, or off. Size, motion and the Mac\u2019s own pet are set in COS Control.', mac: ['Session pet size and motion', 'Characters on the Mac'] },
28
28
  { id: 'system', title: 'System and updates', summary: 'The COS server on this Mac, app updates, and checks when something is off.', mac: ['App updates', 'Troubleshooting'] }
29
29
  ];
30
30
  var DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
@@ -50,8 +50,17 @@
50
50
  }
51
51
  function petBody() {
52
52
  var prefs = W.petPrefs ? W.petPrefs() : { pet: 'mac', show: true };
53
- var out = group('On this browser', '',
54
- row('Show the pet on Today', 'Only here. The pet on your Mac stays as it is.', toggle(prefs.show, 'data-set="pet-show"', 'Show the pet on Today')));
53
+ // 7.0.33 A13 (Anand round 2, slide 9): where the pet shows, one of three, in the choice rows Character uses.
54
+ var where = !prefs.show ? 'off' : prefs.place === 'bar' ? 'bar' : 'today';
55
+ var placeRow = function (v, name, desc) {
56
+ return '<button type="button" class="cx-set-row cx-set-choice" data-set="pet-place" data-place="' + v + '" aria-pressed="' + (where === v) + '">' +
57
+ '<span class="cx-set-text"><span class="cx-set-name">' + esc(name) + '</span><span class="cx-set-desc">' + esc(desc) + '</span></span>' +
58
+ '<span class="value">' + (where === v ? '<span class="check">✓</span>' : '') + '</span></button>';
59
+ };
60
+ var out = group('On this browser', 'Where the pet shows here. The pet on your Mac stays as it is.',
61
+ placeRow('today', 'On Today', 'As it has been: on Today, with what it is doing under it.') +
62
+ placeRow('bar', 'In the top bar', 'On a phone: small, beside Ask, on every page. Tap it to see what it is doing. The Duo shows it on Today.') +
63
+ placeRow('off', 'Off', 'No pet in this browser.'));
55
64
  if (PET.state === 'idle') loadPetCatalog();
56
65
  if (PET.state !== 'ready') {
57
66
  return out + group('Character', '', PET.state === 'error'
@@ -371,12 +380,11 @@
371
380
  }
372
381
  var t = e.target.closest('[data-set]');
373
382
  if (!t || t.disabled) return;
374
- if (t.getAttribute('data-set') === 'pet-show') {
375
- var shown = t.getAttribute('aria-checked') === 'true';
376
- t.setAttribute('aria-checked', String(!shown));
377
- if (W.setPetPrefs) W.setPetPrefs({ show: !shown });
378
- W.toast(!shown ? 'Pet on' : 'Pet off');
379
- return;
383
+ if (t.getAttribute('data-set') === 'pet-place') {
384
+ var v = t.getAttribute('data-place');
385
+ if (W.setPetPrefs) W.setPetPrefs(v === 'off' ? { show: false } : { show: true, place: v });
386
+ W.toast(v === 'off' ? 'Pet off' : v === 'bar' ? 'Pet in the top bar' : 'Pet on Today');
387
+ return render();
380
388
  }
381
389
  if (t.getAttribute('data-set') === 'pet-retry') { PET.state = 'idle'; return render(); }
382
390
  var act = t.getAttribute('data-set');
@@ -439,7 +447,7 @@
439
447
  return;
440
448
  }
441
449
  if (act === 'release') {
442
- if (t.getAttribute('data-armed') !== '1') { t.setAttribute('data-armed', '1'); t.textContent = 'Tap again to release'; return; }
450
+ if (!W.armTap(t, t, 'Tap again to release')) return;
443
451
  // The server fails closed without confirm; the second tap is the confirmation.
444
452
  api('/api/agent-sessions/fences/release', { method: 'POST', body: { target: t.getAttribute('data-target'), confirm: true } })
445
453
  .then(function () { W.toast('Released'); S.data = null; S.state = 'idle'; load(true); }, function (err) { say(err.message, true); });
@@ -252,16 +252,13 @@
252
252
  bar.classList.toggle('hidden', onToday);
253
253
  document.body.classList.toggle('has-navbar', !onToday);
254
254
  var crumbs = $('crumbs');
255
- var section = r.pane === 'usage' ? 'Sessions' : (W.titles[r.pane] || '');
256
255
  var detail = W.crumb();
257
- var parts = ['<li class="crumb root">COS Control</li>'];
258
- if (r.pane === 'settings') parts.push('<li class="crumb current">Settings</li>');
259
- else {
260
- parts.push('<li class="crumb' + (detail || r.pane === 'usage' ? ' dim' : ' current') + '">' + esc(section) + '</li>');
261
- if (r.pane === 'usage') parts.push('<li class="crumb' + (detail ? ' dim' : ' current') + '">Usage</li>');
262
- if (detail) parts.push('<li class="crumb current detail">' + esc(detail) + '</li>');
263
- }
264
- crumbs.innerHTML = parts.join('');
256
+ // 7.0.33 A11: the way you came, the phone's trail (app.js trailNow), not where the page lives. Each step goes back
257
+ // that many steps; this page last. With no path (a menu pick, a deep link), this page's name alone.
258
+ var now = W.trailNow ? W.trailNow(r) : { steps: [], here: detail || W.titles[r.pane] || '' };
259
+ crumbs.innerHTML = (now.steps || []).map(function (s) {
260
+ return '<li class="crumb dim"><a href="' + esc(s.hash) + '" data-trail-back="' + s.back + '">' + esc(s.label) + '</a></li>';
261
+ }).join('') + '<li class="crumb current' + (detail ? ' detail' : '') + '" aria-current="page">' + esc(now.here) + '</li>';
265
262
  $('nav-back').disabled = onToday;
266
263
  // Below 600 px the section name gets the detail too. On a reader that label is hidden (the section button gives way
267
264
  // to Back, which names where it goes), so this shows only on a list that has a detail.
@@ -271,11 +268,9 @@
271
268
 
272
269
  document.addEventListener('click', function (e) {
273
270
  if (e.target.closest('#section-btn')) loadStories(true);
274
- var t = e.target.closest('#nav-home,#nav-back,#nav-settings');
271
+ var t = e.target.closest('#nav-back');
275
272
  if (!t) return;
276
- if (t.id === 'nav-home') W.go('today');
277
- else if (t.id === 'nav-back') W.goBack();
278
- else W.go('settings');
273
+ W.goBack();
279
274
  });
280
275
  document.addEventListener('keydown', function (e) {
281
276
  if (!(e.metaKey || e.ctrlKey) || e.altKey) return;