@gotcos/glasses-server 6.99.29 → 7.0.7
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 +18 -0
- package/CHANGELOG.md +475 -0
- package/README.md +3 -1
- package/managed-runtime-contract.json +3 -0
- package/package.json +1 -1
- package/server/index.ts +29 -7
- package/server/lib/agent-session-output-images.ts +627 -49
- package/server/lib/agent-session-store.ts +24 -11
- package/server/lib/agent-session-tool-images.ts +224 -0
- package/server/lib/agent-session-turns.ts +19 -2
- package/server/lib/api-auth.ts +6 -0
- package/server/lib/ask-work-plan.ts +3 -1
- package/server/lib/calendar-source.ts +491 -0
- package/server/lib/codex-bridge.ts +4 -1
- package/server/lib/codex-run-ledger.ts +29 -0
- package/server/lib/context-builder.ts +2 -1
- package/server/lib/drop-p2-runtime.ts +1 -1
- package/server/lib/drop-store.ts +10 -4
- package/server/lib/learning-needs-you.ts +24 -7
- package/server/lib/live-events.ts +533 -0
- package/server/lib/local-path-redact.ts +272 -0
- package/server/lib/meeting-ask-orientation.ts +40 -6
- package/server/lib/meeting-ask-runner.ts +30 -10
- package/server/lib/morning-brief-runtime.ts +4 -2
- package/server/lib/native-mac-caller.ts +29 -0
- package/server/lib/ping-engine.ts +1 -1
- package/server/lib/ping-sources.ts +11 -6
- package/server/lib/preview-content.ts +172 -0
- package/server/lib/preview-error-code.ts +10 -0
- package/server/lib/preview-folders-setting.ts +133 -0
- package/server/lib/preview-log-throttle.ts +18 -0
- package/server/lib/preview-thumbs.ts +139 -0
- package/server/lib/secret-path-guard.ts +80 -0
- package/server/lib/session-files-setting.ts +88 -0
- package/server/lib/session-links.ts +280 -0
- package/server/lib/thread-turn-queue.ts +10 -0
- package/server/lib/today-brief.ts +119 -28
- package/server/lib/web-memories-bridge.ts +3 -1
- package/server/lib/work-activity.ts +56 -6
- package/server/lib/work-session-activity.ts +260 -0
- package/server/lib/your-move-count.ts +9 -3
- package/server/routes/agent-session-bindings.ts +13 -0
- package/server/routes/agent-session-stream.ts +16 -0
- package/server/routes/agent-sessions.ts +146 -11
- package/server/routes/ask-work-plan.ts +4 -1
- package/server/routes/claude-sessions.ts +5 -1
- package/server/routes/health.ts +35 -0
- package/server/routes/live-events.ts +329 -0
- package/server/routes/permission-broker.ts +69 -5
- package/server/routes/session-preview.ts +200 -0
- package/server/routes/thread-turn-queue.ts +207 -3
- package/server/routes/today-brief.ts +44 -9
- package/server/routes/welcome-context.ts +23 -3
- package/server/routes/work-board.ts +9 -2
- package/server/web-control/app.css +79 -9
- package/server/web-control/app.js +1543 -65
- package/server/web-control/control.css +37 -6
- package/server/web-control/index.html +8 -2
- package/server/web-control/memory-review.js +100 -0
- package/server/web-control/panes/cos-chat.css +51 -6
- package/server/web-control/panes/cos-chat.js +268 -16
- package/server/web-control/panes/meetings.css +23 -4
- package/server/web-control/panes/meetings.js +25 -9
- package/server/web-control/panes/memories.css +18 -3
- package/server/web-control/panes/memories.js +130 -50
- package/server/web-control/panes/messages.js +21 -2
- package/server/web-control/panes/preview.js +318 -0
- package/server/web-control/panes/questions.css +20 -4
- package/server/web-control/panes/questions.js +127 -26
- package/server/web-control/panes/reader-media.js +409 -23
- package/server/web-control/panes/sessions.css +50 -10
- package/server/web-control/panes/settings.js +96 -5
- package/server/web-control/panes/speakers.css +3 -5
- package/server/web-control/panes/speakers.js +7 -4
- package/server/web-control/panes/threads.js +3 -2
- package/server/web-control/panes/today-actions.css +49 -18
- package/server/web-control/panes/today-actions.js +217 -50
- package/server/web-control/panes/work.css +11 -1
- package/server/web-control/panes/work.js +10 -2
- package/server/web-control/shell.js +20 -14
|
@@ -19,9 +19,19 @@
|
|
|
19
19
|
(function () {
|
|
20
20
|
'use strict';
|
|
21
21
|
var W = window.COSWeb;
|
|
22
|
+
// What counts as Needs review: one predicate, shared with the server's count (memory-review.js, loaded by index.html).
|
|
23
|
+
var R = window.COSMemoryReview || null;
|
|
22
24
|
var esc = W.esc;
|
|
23
25
|
var pane = W.$('pane-memories');
|
|
24
26
|
if (!pane) return;
|
|
27
|
+
// 7.0.4 QA: a tab opened before the update has the old index.html, which never loaded memory-review.js, and fetches
|
|
28
|
+
// this pane fresh. Load the predicate here; until it arrives, review rows and counts read as not yet known.
|
|
29
|
+
if (!R) {
|
|
30
|
+
var reviewScript = document.createElement('script');
|
|
31
|
+
reviewScript.src = '/control/memory-review.js';
|
|
32
|
+
reviewScript.onload = function () { R = window.COSMemoryReview || null; if (R && !pane.classList.contains('hidden')) { try { paint(); } catch (_e) { /* the next paint draws it */ } } };
|
|
33
|
+
document.head.appendChild(reviewScript);
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
// ── vocabulary (plain words for the store and event names Control's page shows) ──
|
|
27
37
|
var KIND = { captured: 'Captured', proposed: 'Proposed', promotable: 'Suggested rule', saved: 'Saved', retrieved: 'Recalled',
|
|
@@ -31,7 +41,8 @@
|
|
|
31
41
|
review_ledger: 'Your review decisions', git_versions: 'Skill versions', eval_scores: 'Run checks', reflect_log: 'Daily reflection',
|
|
32
42
|
capture_ledger: 'Capture record', memory_trace: 'Recall record', retrieval_log: 'Recall record', used: 'Use record',
|
|
33
43
|
memory: 'Saved memory', occurrence: 'When it happened', assistant_turn: 'An answer' };
|
|
34
|
-
var GATE = { attribution: 'Who said it is unclear',
|
|
44
|
+
var GATE = { attribution: 'Who said it is unclear', rule_ready: 'Ready to become a rule', low_confidence: 'Ready to become a rule', prune_ready: 'Ready to quarantine',
|
|
45
|
+
contradiction: 'Contradicts another memory', scope: 'No area set', candidate: 'Waiting for your acceptance' };
|
|
35
46
|
var VIEWS = [['recent', 'Recent learning'], ['applied', 'Applied this week'], ['memories', 'All memories'], ['review', 'To review'], ['knowledge', 'Knowledge']];
|
|
36
47
|
// Control's cue review (MeetingAsk.swift CueReviewPane): shown beside the views once the server's cue history answers.
|
|
37
48
|
var CUE_VIEWS = [['cues', 'Cues to rate'], ['rated', 'Rated cues']];
|
|
@@ -109,19 +120,14 @@
|
|
|
109
120
|
// Copy stays in this browser: the same copy the rest of Control Web uses (plain-http fallback included).
|
|
110
121
|
if (op === 'copy') return W.copyText(String(a.text || '')).then(function () { return {}; });
|
|
111
122
|
var limit = READ_LIMIT[op] != null ? READ_LIMIT[op] : (isRead(op, a) ? 45000 : 0);
|
|
112
|
-
return W.api('/api/web-bridge/memories', { method: 'POST', body: { id: ++seq, op: op, args: a }, timeoutMs: limit }).then(
|
|
123
|
+
return W.api('/api/web-bridge/memories', { method: 'POST', body: { id: ++seq, op: op, args: a }, timeoutMs: limit }).then(function (r) {
|
|
113
124
|
if (r && r.ok) return r.details || {};
|
|
114
125
|
var raw = (r && r.message) || 'Your Mac did not answer that.';
|
|
115
126
|
var err = new Error(PLAIN[raw] || raw); err.code = raw; throw err;
|
|
116
127
|
});
|
|
117
128
|
}
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
var UNREACHABLE = 'Could not reach COS on your Mac. Check that the Mac is awake and on Tailscale, then try again.';
|
|
121
|
-
function plainNetError(err) {
|
|
122
|
-
if (err && !err.status && err.code !== 'timeout') { var e = new Error(UNREACHABLE); e.code = 'unreachable'; throw e; }
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
129
|
+
// A request that never reached COS is said in plain words by W.api itself (app.js, 6.99.30: code 'unreachable'),
|
|
130
|
+
// as a timeout is: nothing to translate here.
|
|
125
131
|
function codeOf(e) { return String((e && (e.code || e.message)) || ''); }
|
|
126
132
|
|
|
127
133
|
// ── state ────────────────────────────────────────────────
|
|
@@ -137,7 +143,7 @@
|
|
|
137
143
|
graph: null, graphError: '', setup: null, setupError: '', setupLoading: false, setupBusy: '', progress: null,
|
|
138
144
|
ingest: null, ingestLimit: 10, building: '', folderDraft: '',
|
|
139
145
|
detail: {}, memDetail: {}, passages: {}, loading: {},
|
|
140
|
-
decisions: {}, decisionTs: {}, decided: {},
|
|
146
|
+
decisions: {}, decisionTs: {}, decided: {}, history: {},
|
|
141
147
|
armed: '', reviewBusy: '', reviewIntent: null, bulk: null,
|
|
142
148
|
ask: { q: '', busy: false, answer: null, error: '', cards: [], cardsBusy: false, startedAt: 0, message: '', pending: null },
|
|
143
149
|
focus: null, focusChosen: false, focusResolved: false, knowledgeFocus: null, neighbors: [], graphVisible: null,
|
|
@@ -147,7 +153,7 @@
|
|
|
147
153
|
owner: null, ownerError: '', ownerBusy: false,
|
|
148
154
|
policy: null, policyError: '', policyBusy: false, policyIntent: null, policyLoading: false,
|
|
149
155
|
guard: null, guardError: '', guardBusy: '', guardRun: null, guardLines: 0, guardLoading: false,
|
|
150
|
-
steward: { tab: 'rules', rows: [], cursor: null, busy: false, error: '', intent: null, outcomes: null, serial: 0 }
|
|
156
|
+
steward: { tab: 'rules', rows: [], cursor: null, busy: false, error: '', intent: null, outcomes: null, serial: 0, loaded: false }
|
|
151
157
|
};
|
|
152
158
|
function persist() { W.writeStore(STORE_KEY, { view: S.view, ktab: S.ktab }); }
|
|
153
159
|
var serials = {};
|
|
@@ -390,7 +396,16 @@
|
|
|
390
396
|
var o = outcomeText(e);
|
|
391
397
|
if (o) { var r = typeof e.outcome === 'object' ? e.outcome.result : e.outcome; return { text: 'Checked: ' + o, tone: /^(pass|passed|success)$/.test(r) ? 'ok' : /^(fail|failed)$/.test(r) ? 'bad' : '' }; }
|
|
392
398
|
if (e.needs) return { text: (GATE[e.needs.gate] || 'Needs you') + (e.needs.reason ? '. ' + e.needs.reason : ''), tone: 'warn' };
|
|
393
|
-
if (
|
|
399
|
+
if (e.learning) return { text: 'Learning. ' + (e.learning.reason || 'Not a rule yet'), tone: '' };
|
|
400
|
+
if (isProposal(e) || isPattern(e)) {
|
|
401
|
+
// Needs review only when the predicate says so (a suggested rule at its bar, in the needs list); a suggestion
|
|
402
|
+
// with no next step but Dismiss is listed calmly.
|
|
403
|
+
if (closedHere(e)) return { text: 'Dismissed', tone: '' };
|
|
404
|
+
if (needsLesson(e.lesson_id)) return { text: 'Needs your review', tone: 'warn' };
|
|
405
|
+
var lr = learningFor(e.lesson_id);
|
|
406
|
+
if (lr) return { text: 'Learning. ' + lr.learning.reason, tone: '' };
|
|
407
|
+
return { text: isProposal(e) ? 'Suggested by a scheduled task' : 'Watching. Not a rule yet', tone: '' };
|
|
408
|
+
}
|
|
394
409
|
if (e.event_type === 'dismissed') return { text: 'Dismissed', tone: '' };
|
|
395
410
|
if (e.event_type === 'retrieved') return { text: 'Recalled. Not yet shown to be used.', tone: '' };
|
|
396
411
|
if (e.event_type === 'included') return { text: 'Included in context. Not yet shown to be used.', tone: '' };
|
|
@@ -415,38 +430,65 @@
|
|
|
415
430
|
function plainReason(r) {
|
|
416
431
|
var t = String(r || ''), m;
|
|
417
432
|
if ((m = /^promotable at confidence ([\d.]+) \(< ([\d.]+)\)$/.exec(t))) return 'Confidence ' + m[1] + ', under the ' + m[2] + ' bar for a rule';
|
|
433
|
+
if ((m = /^promotable at confidence ([\d.]+) \(>= ([\d.]+)\)$/.exec(t))) return 'Confidence ' + m[1] + ', at or over the ' + m[2] + ' bar for a rule';
|
|
434
|
+
if (/^promotable at confidence unknown/.test(t)) return 'Confidence not known yet, so it cannot become a rule';
|
|
418
435
|
if ((m = /^names (.+?) near '(.+?)' with no attribution field$/.exec(t))) return 'Mentions ' + m[1].replace(/\b\w/g, function (c) { return c.toUpperCase(); }) + ' near "' + m[2] + '" without saying who said it';
|
|
419
436
|
return t;
|
|
420
437
|
}
|
|
421
|
-
function
|
|
438
|
+
function needsBlock() {
|
|
422
439
|
var ny = (S.recent.info && S.recent.info.needs_you) || (S.status && S.status.needs_you) || null;
|
|
423
|
-
return ny && Array.isArray(ny.items) ? ny
|
|
440
|
+
return R && ny && Array.isArray(ny.items) ? R.normalizeBlock(ny) : null;
|
|
441
|
+
}
|
|
442
|
+
function needsItems() { return R ? R.openItems(needsBlock(), S.decisions) : []; }
|
|
443
|
+
function needsLesson(id) { return !!id && needsItems().some(function (it) { return it.lesson_id === id; }); }
|
|
444
|
+
// An item's latest decision closes it (accepted, dismissed, pruned): it leaves To review for history.
|
|
445
|
+
function closedHere(e) { return !!R && R.reviewState(null, null, decisionFor(e)) === R.HISTORY; }
|
|
446
|
+
function itemRow(it, extra) {
|
|
447
|
+
var fromList = S.review.rows.filter(function (r) { return r.event_id === it.event_id; })[0] ||
|
|
448
|
+
S.recent.rows.filter(function (r) { return r.event_id === it.event_id; })[0];
|
|
449
|
+
var mem = /^mem_/.test(str(it.lesson_id));
|
|
450
|
+
var base = fromList || { event_id: it.event_id, lesson_id: it.lesson_id, ts: it.ts, title: it.title, category: it.category,
|
|
451
|
+
store: it.store || (mem ? 'bot_memory' : null), event_type: mem ? 'captured' : 'promotable', scope: 'unknown', engine: 'unknown',
|
|
452
|
+
target: { kind: mem ? 'memory' : 'pattern', id: it.lesson_id }, source_refs: [], applies_to: [] };
|
|
453
|
+
return Object.assign({}, base, extra);
|
|
454
|
+
}
|
|
455
|
+
// Below-bar suggestions: listed under Learning with their confidence, never counted, no warning tone.
|
|
456
|
+
function learningRows() {
|
|
457
|
+
if (!R) return [];
|
|
458
|
+
return R.learningItems(needsBlock(), S.decisions).filter(function (it) { return it && it.event_id; }).map(function (it) {
|
|
459
|
+
return itemRow(it, { learning: { reason: plainReason(it.reason), confidence: it.confidence, bar: it.bar } });
|
|
460
|
+
});
|
|
424
461
|
}
|
|
462
|
+
function learningFor(id) { return id ? learningRows().filter(function (r) { return r.lesson_id === id; })[0] || null : null; }
|
|
425
463
|
function needsRows() {
|
|
426
|
-
//
|
|
464
|
+
// Only what still needs review: a row decided here leaves the list for history (its reader stays open on it).
|
|
427
465
|
return needsItems().map(function (it) {
|
|
428
|
-
|
|
429
|
-
S.recent.rows.filter(function (r) { return r.event_id === it.event_id; })[0];
|
|
430
|
-
var mem = /^mem_/.test(str(it.lesson_id));
|
|
431
|
-
var base = fromList || { event_id: it.event_id, lesson_id: it.lesson_id, ts: it.ts, title: it.title, category: it.category,
|
|
432
|
-
store: it.store || (mem ? 'bot_memory' : null), event_type: mem ? 'captured' : 'promotable', scope: 'unknown', engine: 'unknown',
|
|
433
|
-
target: { kind: mem ? 'memory' : 'pattern', id: it.lesson_id }, source_refs: [], applies_to: [] };
|
|
434
|
-
return Object.assign({}, base, { needs: { gate: it.gate, reason: plainReason(it.reason) } });
|
|
466
|
+
return itemRow(it, { needs: { gate: it.gate, reason: plainReason(it.reason) } });
|
|
435
467
|
});
|
|
436
468
|
}
|
|
469
|
+
// The same count the server hands Today and the rail (memory-review.js count), less what was decided on this page.
|
|
437
470
|
function needsCount() {
|
|
438
471
|
var s = S.status || {}, ny = (S.recent.info && S.recent.info.needs_you) || s.needs_you;
|
|
439
|
-
|
|
440
|
-
if (
|
|
472
|
+
var n = ny && R ? R.count(R.normalizeBlock(ny), S.decisions) : null;
|
|
473
|
+
if (n != null) return n;
|
|
441
474
|
if (typeof s.learningToReview === 'number') return s.learningToReview;
|
|
442
475
|
return null;
|
|
443
476
|
}
|
|
477
|
+
// To review's other rows: proposed changes still open, and suggested rules only once the predicate counts them.
|
|
478
|
+
function reviewRestRows(seen) {
|
|
479
|
+
var learn = {};
|
|
480
|
+
learningRows().forEach(function (r) { learn[r.lesson_id] = 1; });
|
|
481
|
+
return S.review.rows.filter(function (e) { return !seen[e.event_id] && !closedHere(e) && !(isPattern(e) && learn[e.lesson_id]); });
|
|
482
|
+
}
|
|
444
483
|
function findRow(id) {
|
|
445
|
-
var all = needsRows().concat(S.review.rows, S.recent.rows, S.applied.rows);
|
|
484
|
+
var all = needsRows().concat(learningRows(), S.review.rows, S.recent.rows, S.applied.rows);
|
|
446
485
|
for (var i = 0; i < all.length; i++) if (all[i].event_id === id) return all[i];
|
|
486
|
+
if (S.history[id]) return S.history[id];
|
|
447
487
|
var d = S.detail[id];
|
|
448
488
|
return d && !d._error && d.event_id ? d : null;
|
|
449
489
|
}
|
|
490
|
+
// A row decided here leaves the lists; its reader keeps it so Restore stays one tap away.
|
|
491
|
+
function keepForReader(e) { if (e && e.event_id) S.history[e.event_id] = e; }
|
|
450
492
|
function fullRow(e) { var d = S.detail[e.event_id]; return Object.assign({}, e, d && !d._error ? d : {}); }
|
|
451
493
|
function rewardOn() { return !!(S.status && S.status.learningRewardEnabled === true); }
|
|
452
494
|
function rankingSentence() { return rewardOn() ? '<p>This lesson now ranks ahead of unused memories like it in later recall.</p>' : ''; }
|
|
@@ -806,17 +848,20 @@
|
|
|
806
848
|
(L.cursor ? moreBtn('more-recent', 'Load more', L.loading) : '') + coverageNote(L.coverage);
|
|
807
849
|
}
|
|
808
850
|
function reviewListHtml() {
|
|
809
|
-
var L = S.review, sel = selectedId(), needs = needsRows(), seen = {};
|
|
851
|
+
var L = S.review, sel = selectedId(), needs = needsRows(), learning = learningRows(), seen = {};
|
|
810
852
|
needs.forEach(function (e) { seen[e.event_id] = 1; });
|
|
811
|
-
|
|
853
|
+
learning.forEach(function (e) { seen[e.event_id] = 1; });
|
|
854
|
+
var rest = reviewRestRows(seen);
|
|
812
855
|
var other = rest.filter(function (e) { return !isPruneReady(e); }), ready = rest.filter(isPruneReady);
|
|
813
856
|
var out = '';
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
if (!
|
|
819
|
-
if (
|
|
857
|
+
if (needs.length) out += groupHead('Needs you now', fmt(needsCount() || needs.length) + ' open') + '<div class="cx-list">' + needs.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>';
|
|
858
|
+
var learnHtml = learning.length ? groupHead('Learning', fmt(learning.length) + ' ' + plural(learning.length, 'suggestion', 'suggestions')) +
|
|
859
|
+
'<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
|
+
'<div class="cx-list">' + learning.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>' : '';
|
|
861
|
+
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;
|
|
863
|
+
if (!needs.length && !rest.length) return groupHead('To review') + stateBox('You are up to date.', 'Nothing is waiting for your decision.') + learnHtml;
|
|
864
|
+
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>';
|
|
820
865
|
if (ready.length) {
|
|
821
866
|
var open = ready.filter(function (e) { return !decisionFor(e); }).length, b = S.bulk;
|
|
822
867
|
out += groupHead('Ready to quarantine', open ? fmt(open) + ' open' : 'All decided') +
|
|
@@ -826,7 +871,7 @@
|
|
|
826
871
|
'<div class="cx-list">' + ready.map(function (e) { return rowHtml(e, e.event_id === sel); }).join('') + '</div>';
|
|
827
872
|
}
|
|
828
873
|
if (L.cursor) out += moreBtn('more-review', 'Load more to review', L.loading);
|
|
829
|
-
return out;
|
|
874
|
+
return out + learnHtml;
|
|
830
875
|
}
|
|
831
876
|
function memSearchHtml() {
|
|
832
877
|
return '<form class="cx-search mm-search" data-mm-form="mem-search" role="search"><input class="field" type="search" id="mm-mem-q" placeholder="Search topics, ideas…" value="' + esc(S.mem.q) + '" autocomplete="off" enterkeyhint="search" aria-label="Search memories">' +
|
|
@@ -869,7 +914,7 @@
|
|
|
869
914
|
if (!isWide() || S.view === 'cues' || S.view === 'rated') return '';
|
|
870
915
|
if (S.view === 'knowledge') return S.ktab;
|
|
871
916
|
if (S.view === 'memories') { var m = S.mem.hits != null ? S.mem.hits : S.mem.rows; return m[0] ? m[0].id : ''; }
|
|
872
|
-
var rows = S.view === 'applied' ? S.applied.rows : S.view === 'review' ? needsRows().concat(
|
|
917
|
+
var rows = S.view === 'applied' ? S.applied.rows : S.view === 'review' ? needsRows().concat(reviewRestRows({}), learningRows()) : S.recent.rows;
|
|
873
918
|
return rows[0] ? rows[0].event_id : '';
|
|
874
919
|
}
|
|
875
920
|
|
|
@@ -879,8 +924,9 @@
|
|
|
879
924
|
}
|
|
880
925
|
function quote(text) { return '<blockquote class="mm-quote">' + esc(text) + '</blockquote>'; }
|
|
881
926
|
function readerHead(title, facts, actions) {
|
|
927
|
+
var full = SPLIT_KIT.fullButton(SPLIT.key, SPLIT.controls);
|
|
882
928
|
return '<div class="mm-reader-head"><h2 class="mm-title">' + esc(title) + '</h2>' + (facts ? '<p class="mm-sub">' + facts + '</p>' : '') +
|
|
883
|
-
(actions ? '<div class="mm-actions">' + actions + '</div>' : '') + '</div>';
|
|
929
|
+
(actions || full ? '<div class="mm-actions">' + (actions || '') + full + '</div>' : '') + '</div>';
|
|
884
930
|
}
|
|
885
931
|
function eventReader(id) {
|
|
886
932
|
var base = findRow(id);
|
|
@@ -899,7 +945,8 @@
|
|
|
899
945
|
var facts = ['<span class="mm-kind">' + esc(o ? 'Checked: ' + o : kindLabel(e)) + '</span>', esc(storeName(e.store)), esc(known(e.scope)), esc(known(e.engine) !== known(e.scope) ? known(e.engine) : ''), esc(when(e.ts))].filter(function (x) { return x && x !== '<span class="mm-kind"></span>'; }).join(' · ');
|
|
900
946
|
var actions = btn('open-sources', 'Open sources', { id: e.event_id }) + btn('explore', 'Explore in graph', { id: e.event_id }) + btn('copy-open', 'Copy context', { id: e.event_id, cls: 'primary' });
|
|
901
947
|
var note = '';
|
|
902
|
-
if (e.needs) note += '<div class="cx-notice warn">' + esc((GATE[e.needs.gate] || 'Needs you') + (e.needs.reason ? ': ' + e.needs.reason : '') + '.') + '</div>';
|
|
948
|
+
if (e.needs && !closedHere(e)) note += '<div class="cx-notice warn">' + esc((GATE[e.needs.gate] || 'Needs you') + (e.needs.reason ? ': ' + e.needs.reason : '') + '.') + '</div>';
|
|
949
|
+
else if (e.learning || learningFor(e.lesson_id)) note += '<div class="cx-notice">' + esc('Learning: ' + ((e.learning || learningFor(e.lesson_id).learning).reason || 'not a rule yet') + '. It moves to Needs you now on its own once its confidence reaches the bar.') + '</div>';
|
|
903
950
|
if (full && full._error && !(e.store === 'bot_memory')) note += '<div class="cx-notice">' + esc(full._error) + '</div>';
|
|
904
951
|
else if (!full && !(e.store === 'bot_memory' && e.lesson_id) && S.loading[e.event_id]) note += '<p class="cx-note">Reading the full record…</p>';
|
|
905
952
|
if (e.detail && e.detail.truncated) note += '<div class="cx-notice">The record was shortened to fit. Your Mac holds more than shown here.</div>';
|
|
@@ -1074,7 +1121,15 @@
|
|
|
1074
1121
|
function renderMarkdown(md) {
|
|
1075
1122
|
var lines = esc(md || '').split('\n'), out = [], ul = null, table = null;
|
|
1076
1123
|
function inline(s) { return s.replace(/`([^`]+)`/g, '<code>$1</code>').replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>'); }
|
|
1077
|
-
function flush() { if (ul) { out.push('<ul>' + ul.join('') + '</ul>'); ul = null; } if (table) { out.push(
|
|
1124
|
+
function flush() { if (ul) { out.push('<ul>' + ul.join('') + '</ul>'); ul = null; } if (table) { out.push(tableHtml(table)); table = null; } }
|
|
1125
|
+
// Duo tables: the first row is the header; the shared column floors (app.js tableFit) set how far each column shrinks.
|
|
1126
|
+
function tableHtml(rows) {
|
|
1127
|
+
var fit = W.tableFit(rows);
|
|
1128
|
+
return '<div class="mm-table tf-wrap"><table class="' + fit.tableClass + ' tf-grid">' + rows.map(function (cells, n) {
|
|
1129
|
+
var tag = n ? 'td' : 'th';
|
|
1130
|
+
return '<tr>' + cells.map(function (c, k) { var w = fit.width(k, !n); return '<' + tag + (w ? ' style="' + w + '"' : '') + '>' + fit.cell(k, inline(c), !n) + '</' + tag + '>'; }).join('') + '</tr>';
|
|
1131
|
+
}).join('') + '</table></div>';
|
|
1132
|
+
}
|
|
1078
1133
|
lines.forEach(function (raw) {
|
|
1079
1134
|
var line = raw.replace(/\s+$/, '');
|
|
1080
1135
|
if (!line.trim()) { flush(); return; }
|
|
@@ -1085,9 +1140,8 @@
|
|
|
1085
1140
|
if (/^\s*\|.*\|\s*$/.test(line)) {
|
|
1086
1141
|
if (/^\s*\|[\s:|-]+\|\s*$/.test(line)) return;
|
|
1087
1142
|
if (ul) flush();
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
table.push('<tr>' + cells.map(function (c) { return '<' + tag + '>' + c + '</' + tag + '>'; }).join('') + '</tr>'); return;
|
|
1143
|
+
table = table || [];
|
|
1144
|
+
table.push(line.trim().replace(/^\||\|$/g, '').split('|').map(function (c) { return c.trim(); })); return;
|
|
1091
1145
|
}
|
|
1092
1146
|
flush(); out.push('<p>' + inline(line) + '</p>');
|
|
1093
1147
|
});
|
|
@@ -1329,7 +1383,7 @@
|
|
|
1329
1383
|
function closeModal() {
|
|
1330
1384
|
var m = S.modal; if (!m) return;
|
|
1331
1385
|
if (m.kind === 'merge' && S.merge && (S.merge.phase === 'preview' || S.merge.phase === 'loading')) { var pend = S.merge; S.merge = null; if (pend.resolve) pend.resolve(false); W.toast('Nothing was changed.'); }
|
|
1332
|
-
if (m.kind === 'steward') S.steward.serial++;
|
|
1386
|
+
if (m.kind === 'steward') S.steward.serial++; // supersedes the read in flight; the next open starts its own (stewardLoad)
|
|
1333
1387
|
S.modal = null; paintModal(); if (m.opener && m.opener.focus) { try { m.opener.focus({ preventScroll: true }); } catch (_e) {} }
|
|
1334
1388
|
}
|
|
1335
1389
|
function modalHtml() {
|
|
@@ -1435,16 +1489,28 @@
|
|
|
1435
1489
|
'<ul class="mm-log">' + flagged.concat(review).slice(0, 60).map(function (x) { return '<li><b class="' + (x.verdict === 'prune' ? 'mm-bad' : '') + '">' + (x.verdict === 'prune' ? (x.applied ? 'Quarantined' : 'Would quarantine') : 'For you') + '</b> <span>' + esc(clip(x.excerpt || x.id, 160)) + '</span><span class="mm-muted">' + esc((x.reasons || []).join('; ')) + (x.by === 'model' ? ' (model)' : '') + '</span></li>'; }).join('') + '</ul>' : '') + '</section>';
|
|
1436
1490
|
}
|
|
1437
1491
|
// Stewardship (memories/memory-stewardship.js, ported): standing rules, current sources and outcome evidence.
|
|
1492
|
+
// A read never waits on an older one: each takes a new serial and only the newest may answer. Closing the sheet or
|
|
1493
|
+
// switching tabs supersedes the read in flight, and every read ends (an answer, an error, or STEWARD_READ_MS) so
|
|
1494
|
+
// "Working…" can never outlive it. 7.0.2 kept `busy` set when a read was superseded, and the next open or tab
|
|
1495
|
+
// switch returned early on it: Outcome evidence read "Working…" for good.
|
|
1496
|
+
var STEWARD_READ_MS = 30000;
|
|
1438
1497
|
function stewardLoad(more) {
|
|
1439
|
-
var T = S.steward; if (T.
|
|
1440
|
-
T.busy = true; T.error = ''; var serial = ++T.serial;
|
|
1498
|
+
var T = S.steward; if (T.intent) return;
|
|
1499
|
+
T.busy = true; T.error = ''; T.loaded = false; var serial = ++T.serial;
|
|
1441
1500
|
var action = T.tab === 'rules' ? 'rule_page' : T.tab === 'sources' ? 'source_status' : 'trace_summary', body = { action: action };
|
|
1442
1501
|
if (action === 'rule_page') { body.limit = 25; body.cursor = more ? T.cursor : null; }
|
|
1443
1502
|
paintModal();
|
|
1444
|
-
|
|
1503
|
+
var timer = null;
|
|
1504
|
+
var late = new Promise(function (_ok, fail) { timer = setTimeout(function () { fail(new Error('Your Mac did not answer in ' + (STEWARD_READ_MS / 1000) + ' seconds. Reload to try again.')); }, STEWARD_READ_MS); });
|
|
1505
|
+
Promise.race([call('workspace.request', body), late]).then(function (d) {
|
|
1445
1506
|
if (serial !== T.serial) return;
|
|
1507
|
+
d = d || {};
|
|
1446
1508
|
if (action === 'trace_summary') T.outcomes = d; else { T.rows = more ? T.rows.concat(d.items || []) : d.items || []; T.cursor = d.next_cursor || null; }
|
|
1447
|
-
}, function (e) { if (serial !== T.serial) return; T.error = e.message; }).then(function () {
|
|
1509
|
+
}, function (e) { if (serial !== T.serial) return; T.error = e.message; }).then(function () {
|
|
1510
|
+
clearTimeout(timer);
|
|
1511
|
+
if (serial !== T.serial) return;
|
|
1512
|
+
T.busy = false; T.loaded = true; paintModal();
|
|
1513
|
+
});
|
|
1448
1514
|
}
|
|
1449
1515
|
function stewardMutate(body) {
|
|
1450
1516
|
var T = S.steward; if (T.busy) return;
|
|
@@ -1484,8 +1550,9 @@
|
|
|
1484
1550
|
body += T.rows.length ? T.rows.map(function (r) { return '<section class="cx-card mm-card"><h4>' + esc(r.title || r.id) + '</h4><p class="mm-muted">Revision ' + esc(r.revision) + ' · ' + esc(when(r.captured_at)) + ' · ' + esc(r.projection) + '</p>' + sbtn('steward-refresh', 'Refresh this document', r.id) + '</section>'; }).join('') :
|
|
1485
1551
|
(!T.busy ? '<p>No granted documents right now. The owner grants documents on the Mac.</p>' : '');
|
|
1486
1552
|
} else {
|
|
1487
|
-
var d = T.outcomes;
|
|
1488
|
-
|
|
1553
|
+
var d = T.outcomes, none = d && !(Number(d.unique_runs) > 0) && !(Number(d.checked_eligible_runs) > 0) && !(Number(d.inconclusive_runs) > 0);
|
|
1554
|
+
if (!T.busy && !T.error && T.loaded && (!d || none)) body += '<p>No outcome evidence yet.</p>';
|
|
1555
|
+
body += d && !none ? '<dl class="mm-grid"><dt>Observed runs</dt><dd>' + fmt(d.unique_runs || 0) + '</dd><dt>Checked runs that count</dt><dd>' + fmt(d.checked_eligible_runs || 0) + '</dd><dt>Not conclusive</dt><dd>' + fmt(d.inconclusive_runs || 0) + '</dd><dt>Days observed</dt><dd>' + fmt(d.observation_days || 0) + '</dd><dt>Evidence bar</dt><dd>' + (d.evidence_gate_met ? 'Met' : 'Not met yet') + (d.rate_limit_reason ? '. ' + esc(d.rate_limit_reason) : '') + '</dd></dl>' +
|
|
1489
1556
|
'<details><summary>Show the raw numbers</summary><pre class="mm-pre">' + esc(JSON.stringify(d, null, 2)) + '</pre></details>' : '';
|
|
1490
1557
|
body += '<p class="mm-muted">Being recalled, being included in context, a model saying it applied something and an independently checked result are four different things. Too little evidence does not mean things improved.</p>';
|
|
1491
1558
|
}
|
|
@@ -1494,7 +1561,7 @@
|
|
|
1494
1561
|
function stewardAct(act, id) {
|
|
1495
1562
|
var T = S.steward, dr = S.drafts;
|
|
1496
1563
|
if (act === 'steward-retry') return stewardMutate(T.intent);
|
|
1497
|
-
if (act === 'steward-tab') { if (T.intent) return; T.tab = id; T.rows = []; T.cursor = null; T.outcomes = null; return stewardLoad(false); }
|
|
1564
|
+
if (act === 'steward-tab') { if (T.intent) return; T.tab = id; T.rows = []; T.cursor = null; T.outcomes = null; T.loaded = false; return stewardLoad(false); }
|
|
1498
1565
|
if (act === 'steward-reload') return stewardLoad(false);
|
|
1499
1566
|
if (act === 'steward-more') return stewardLoad(true);
|
|
1500
1567
|
if (act === 'steward-propose') {
|
|
@@ -1806,6 +1873,7 @@
|
|
|
1806
1873
|
// Restore is Accept on the wire: the bridge returns a quarantined memory to recall before marking it accepted.
|
|
1807
1874
|
call('memory.review', { id: id, decision: decision === 'restore' ? 'accept' : decision }).then(function (d) {
|
|
1808
1875
|
var row = d.decision || {};
|
|
1876
|
+
S.review.rows.concat(needsRows(), S.recent.rows).forEach(function (r) { if (r.lesson_id === id) keepForReader(r); });
|
|
1809
1877
|
S.decisions[id] = row.decision || (decision === 'prune' ? 'pruned' : 'accepted'); S.decisionTs[id] = new Date().toISOString(); S.decided[id] = 1;
|
|
1810
1878
|
if (decision === 'prune') { S.mem.rows = S.mem.rows.filter(function (m) { return m.id !== id; }); }
|
|
1811
1879
|
W.toast(decision === 'prune' ? 'Memory quarantined.' : decision === 'restore' ? 'Memory restored to recall.' : 'Memory accepted.');
|
|
@@ -1834,6 +1902,7 @@
|
|
|
1834
1902
|
function decide(eventId, decision) {
|
|
1835
1903
|
var e = findRow(eventId); if (!e) return;
|
|
1836
1904
|
call('learning.decide', { id: e.lesson_id, decision: decision }).then(function () {
|
|
1905
|
+
keepForReader(e);
|
|
1837
1906
|
S.decisions[e.lesson_id] = decision; if (decision === 'dismissed') S.decided[e.lesson_id] = 1; else delete S.decided[e.lesson_id];
|
|
1838
1907
|
W.toast(decision === 'dismissed' ? 'Suggestion dismissed.' : 'Suggestion restored.');
|
|
1839
1908
|
loadReview(false); afterWrite();
|
|
@@ -1977,8 +2046,15 @@
|
|
|
1977
2046
|
}
|
|
1978
2047
|
|
|
1979
2048
|
// ── painting ─────────────────────────────────────────────
|
|
2049
|
+
// Duo tables: the divider between the list and the reader from 760 px (app.js splitKit; the same limits as Sessions),
|
|
2050
|
+
// with its own remembered width, and Full width in a reader's header.
|
|
2051
|
+
var SPLIT = { key: 'cos_web_memories_split', label: 'Memories list', controls: 'mm-list-col', media: '(min-width: 760px)', listMin: 240, readerMin: 380, handleW: 18 };
|
|
2052
|
+
// A page without the kit (a test's stand-in W) draws no divider and keeps working.
|
|
2053
|
+
var SPLIT_KIT = W.splitKit || { handle: function () { return ''; }, mount: function () { return null; }, fullButton: function () { return ''; } };
|
|
2054
|
+
var SPLIT_HANDLE = SPLIT_KIT.handle({ id: 'mm-handle', controls: SPLIT.controls, label: SPLIT.label });
|
|
1980
2055
|
var SKELETON = '<div class="cx mm" data-tint="memories" id="mm-root"><div id="mm-hero"></div><div class="mm-controls" id="mm-views"></div><p class="mm-counts" id="mm-counts"></p>' +
|
|
1981
|
-
'<div class="split mm-split" id="mm-split"><div class="split-list mm-list" id="mm-list-col"><div id="mm-tools"></div><div id="mm-list"></div></div
|
|
2056
|
+
'<div class="split mm-split" id="mm-split"><div class="split-list mm-list" id="mm-list-col"><div id="mm-tools"></div><div id="mm-list"></div></div>' +
|
|
2057
|
+
SPLIT_HANDLE + '<div class="reader mm-reader" id="mm-reader"></div></div>' +
|
|
1982
2058
|
'<p class="mm-foot" id="mm-foot"></p></div><div id="mm-modal-host"></div>';
|
|
1983
2059
|
function write(node, html) { if (!node || node.__mm === html) return false; node.innerHTML = html; node.__mm = html; return true; }
|
|
1984
2060
|
var paintTimer = null;
|
|
@@ -1987,6 +2063,7 @@
|
|
|
1987
2063
|
var r = W.parseRoute(); if (r.pane !== 'memories') return;
|
|
1988
2064
|
if (!el('mm-root')) pane.innerHTML = SKELETON;
|
|
1989
2065
|
var reading = readerArgs(r).length > 0, split = el('mm-split');
|
|
2066
|
+
SPLIT_KIT.mount(split, el('mm-handle'), SPLIT);
|
|
1990
2067
|
pane.classList.toggle('reading', reading); if (split) split.classList.toggle('reading', reading);
|
|
1991
2068
|
// The cue views are one column at every width, like Control's cue review (a card, not a list beside a reader).
|
|
1992
2069
|
if (split) split.classList.toggle('solo', !reading && (S.view === 'cues' || S.view === 'rated'));
|
|
@@ -2012,6 +2089,9 @@
|
|
|
2012
2089
|
var html = readerHtml(r);
|
|
2013
2090
|
if (readerArgs(r).length && W.crumb && W.crumb() !== S.crumbText) W.setCrumb(S.crumbText);
|
|
2014
2091
|
if (write(node, html) && el('mm-graph-host')) mountGraph();
|
|
2092
|
+
// Full width hides the list only while a reader with the button is open, so the list is never hidden behind a
|
|
2093
|
+
// placeholder with no way back.
|
|
2094
|
+
var split = el('mm-split'); if (split) split.classList.toggle('mm-fullable', !!(node.querySelector && node.querySelector('[data-split-full]')));
|
|
2015
2095
|
document.body.classList.toggle('mm-graph-open', S.graphFull && !!el('mm-graph-host'));
|
|
2016
2096
|
}
|
|
2017
2097
|
function crumb(t) { S.crumbText = String(t || ''); }
|
|
@@ -401,15 +401,34 @@
|
|
|
401
401
|
}).join('') + '</div>';
|
|
402
402
|
}
|
|
403
403
|
// Drop it on: the list column and the chat column. Built once; each column then paints only itself.
|
|
404
|
+
// Duo tables: the divider between the two columns from 860 px (app.js splitKit), with its own remembered width. Its
|
|
405
|
+
// 24 px column replaces the split's 24 px gap, so the default layout is unchanged. The list keeps 240 px like Sessions;
|
|
406
|
+
// the chat keeps 440, not 380, because its composer (text box, camera, file, Ask, Drop) squeezes the text box to about
|
|
407
|
+
// 90 px at 380 (headless render at 1000x703).
|
|
408
|
+
// A page without the kit (a test's stand-in W) draws no divider and keeps working.
|
|
409
|
+
var SPLIT_KIT = W.splitKit || { handle: function () { return ''; }, mount: function () { return null; } };
|
|
410
|
+
var SPLIT = { key: 'cos_web_messages_split', label: 'Message list', controls: 'msg-left', sized: '.msg-left', media: '(min-width: 860px)', listMin: 240, readerMin: 440, handleW: 24 };
|
|
404
411
|
function splitHost() {
|
|
405
412
|
var split = pane.querySelector('.msg-split');
|
|
406
413
|
if (!split) {
|
|
407
|
-
pane.innerHTML = '<div class="msg-split"><div class="msg-left" id="msg-left"></div
|
|
414
|
+
pane.innerHTML = '<div class="msg-split"><div class="msg-left" id="msg-left"></div>' +
|
|
415
|
+
SPLIT_KIT.handle({ id: 'msg-handle', controls: SPLIT.controls, label: SPLIT.label }) + '<div class="msg-right" id="msg-right"></div></div>';
|
|
408
416
|
split = pane.querySelector('.msg-split');
|
|
409
417
|
}
|
|
418
|
+
SPLIT_KIT.mount(split, $('msg-handle'), SPLIT);
|
|
410
419
|
split.setAttribute('data-show', current[0] === 'cos' ? 'chat' : 'list');
|
|
420
|
+
chatRoute(current[0] === 'cos');
|
|
411
421
|
return split;
|
|
412
422
|
}
|
|
423
|
+
// 7.0.2 (A2, the Duo audit): the workspace's bottom padding (app.css, +96 px folded and +40 px from 600 px) is room for
|
|
424
|
+
// a bottom bar that no longer exists. Under the chat's sticky composer it was blank page: focusing the box scrolled to
|
|
425
|
+
// it, and after the keyboard closed it stayed as a band under Model and Effort. On the COS chat route the workspace
|
|
426
|
+
// carries ws-chat, and app.css drops that padding. Any other route takes the class off.
|
|
427
|
+
function chatRoute(on) {
|
|
428
|
+
var ws = $('workspace');
|
|
429
|
+
if (ws && ws.classList) ws.classList.toggle('ws-chat', !!on);
|
|
430
|
+
}
|
|
431
|
+
if (W.onRoute) W.onRoute(function (r) { if (!r || r.pane !== 'messages' || r.args[0] !== 'cos') chatRoute(false); });
|
|
413
432
|
function renderList() {
|
|
414
433
|
var focus = document.activeElement && document.activeElement.id === 'msg-q';
|
|
415
434
|
var caret = focus ? document.activeElement.selectionStart : null;
|
|
@@ -452,7 +471,7 @@
|
|
|
452
471
|
var a = atts(t);
|
|
453
472
|
return '<div class="cx-card you"><div class="cx-card-label">YOU</div><div class="cx-card-body" data-hl>' + esc(String(t.query || '').trim() || '(empty query)').replace(/\n/g, '<br>') + '</div></div>' +
|
|
454
473
|
stripHtml(a.filter(isUserAtt), true, msgKey) +
|
|
455
|
-
'<div class="cx-card"><div class="cx-card-label">COS</div><div class="cx-card-body md" data-hl>' + W.md(String(t.text || '')) + '</div></div>' +
|
|
474
|
+
'<div class="cx-card"><div class="cx-card-label">COS</div><div class="cx-card-body md" data-hl>' + W.md(W.structureLensAnswer(String(t.text || ''))) + '</div></div>' +
|
|
456
475
|
stripHtml(a.filter(function (x) { return !isUserAtt(x); }), false, msgKey);
|
|
457
476
|
}
|
|
458
477
|
function copyTurnText(t) {
|