instar 1.3.1609 → 1.3.1611
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/dashboard/console/console.css +19 -9
- package/dashboard/console/console.js +27 -5
- package/dashboard/console/home-model.js +20 -0
- package/dashboard/console/nav-model.js +12 -14
- package/dashboard/console/schedule.js +4 -4
- package/dashboard/console/sessions-model.js +18 -0
- package/dashboard/console/sessions.js +55 -19
- package/dashboard/console/ui.js +11 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +12 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AppConversationService.d.ts +22 -0
- package/dist/core/AppConversationService.d.ts.map +1 -1
- package/dist/core/AppConversationService.js +49 -4
- package/dist/core/AppConversationService.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +3 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +13 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/VoiceTranscription.d.ts +40 -0
- package/dist/core/VoiceTranscription.d.ts.map +1 -0
- package/dist/core/VoiceTranscription.js +86 -0
- package/dist/core/VoiceTranscription.js.map +1 -0
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +20 -7
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +130 -75
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/shared/channelIdentity.d.ts +11 -0
- package/dist/messaging/shared/channelIdentity.d.ts.map +1 -1
- package/dist/messaging/shared/channelIdentity.js +42 -0
- package/dist/messaging/shared/channelIdentity.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +10 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +29 -4
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/slack/types.d.ts +5 -0
- package/dist/messaging/slack/types.d.ts.map +1 -1
- package/dist/messaging/slack/types.js.map +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.d.ts +2 -0
- package/dist/messaging/telegram-origin/OriginLegacy.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.js +20 -2
- package/dist/messaging/telegram-origin/OriginLegacy.js.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.d.ts +4 -0
- package/dist/monitoring/GreenPrAutoMerger.d.ts.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.js +27 -9
- package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
- package/dist/monitoring/SessionMonitor.d.ts +21 -0
- package/dist/monitoring/SessionMonitor.d.ts.map +1 -1
- package/dist/monitoring/SessionMonitor.js +84 -1
- package/dist/monitoring/SessionMonitor.js.map +1 -1
- package/dist/native/instar-desktop-host +0 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +4 -1
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/poolPeerFetch.d.ts +30 -7
- package/dist/server/poolPeerFetch.d.ts.map +1 -1
- package/dist/server/poolPeerFetch.js +74 -16
- package/dist/server/poolPeerFetch.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +14 -2
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-no-direct-llm-http.js +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/data/state-coherence-registry.json +18 -0
- package/upgrades/1.3.1610.md +62 -0
- package/upgrades/1.3.1611.md +64 -0
- package/upgrades/side-effects/attention-names-not-ids.eli16.md +7 -0
- package/upgrades/side-effects/attention-names-not-ids.md +36 -0
- package/upgrades/side-effects/console-failures-link-to-problem.eli16.md +7 -0
- package/upgrades/side-effects/console-failures-link-to-problem.md +31 -0
- package/upgrades/side-effects/console-mobile-composer-work-tiles.eli16.md +7 -0
- package/upgrades/side-effects/console-mobile-composer-work-tiles.md +32 -0
- package/upgrades/side-effects/console-partial-read-grace.eli16.md +9 -0
- package/upgrades/side-effects/console-partial-read-grace.md +33 -0
- package/upgrades/side-effects/green-pr-skip-unactionable-head.eli16.md +7 -0
- package/upgrades/side-effects/green-pr-skip-unactionable-head.md +41 -0
- package/upgrades/side-effects/mobile-native-chat-media-voice.md +92 -0
- package/upgrades/side-effects/origin-legacy-boot-beacon-replay.eli16.md +7 -0
- package/upgrades/side-effects/origin-legacy-boot-beacon-replay.md +36 -0
- package/upgrades/side-effects/pool-read-cloudflare-edge-fallthrough.eli16.md +7 -0
- package/upgrades/side-effects/pool-read-cloudflare-edge-fallthrough.md +39 -0
- package/upgrades/side-effects/session-history-topic-backfill.eli16.md +9 -0
- package/upgrades/side-effects/session-history-topic-backfill.md +39 -0
- package/upgrades/side-effects/slack-session-stopped-once.eli16.md +28 -0
- package/upgrades/side-effects/slack-session-stopped-once.md +89 -0
- package/upgrades/side-effects/topic-probe-no-repeat-refusals.eli16.md +7 -0
- package/upgrades/side-effects/topic-probe-no-repeat-refusals.md +34 -0
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
--ease: cubic-bezier(.4, 0, .2, 1);
|
|
58
58
|
--dur: .18s;
|
|
59
59
|
--nav-h: 64px;
|
|
60
|
+
/* The phone's bottom inset. Inside the Instar app this is the app's own tab
|
|
61
|
+
bar (about 68px), so everything that sits above the bottom nav adds it. */
|
|
62
|
+
--safe-b: env(safe-area-inset-bottom, 0px);
|
|
63
|
+
--nav-total: calc(var(--nav-h) + var(--safe-b));
|
|
60
64
|
color-scheme: dark;
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -131,14 +135,14 @@ button { font: inherit; color: inherit; }
|
|
|
131
135
|
.chip:hover { border-color: var(--hairline-strong); color: var(--text); }
|
|
132
136
|
.icon-btn { width: 34px; padding: 0; justify-content: center; }
|
|
133
137
|
|
|
134
|
-
.main { padding: var(--s-5) var(--s-4) calc(var(--nav-
|
|
138
|
+
.main { padding: var(--s-5) var(--s-4) calc(var(--nav-total) + 32px); max-width: 1120px; width: 100%; margin: 0 auto; }
|
|
135
139
|
|
|
136
140
|
/* Bottom nav (phone) → sidebar (desktop) */
|
|
137
141
|
.nav {
|
|
138
142
|
position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
|
|
139
143
|
display: grid; grid-template-columns: repeat(5, 1fr);
|
|
140
|
-
height:
|
|
141
|
-
padding-bottom:
|
|
144
|
+
height: var(--nav-total);
|
|
145
|
+
padding-bottom: var(--safe-b);
|
|
142
146
|
background: color-mix(in srgb, var(--surface-solid) 86%, transparent);
|
|
143
147
|
backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
|
|
144
148
|
border-top: 1px solid var(--hairline);
|
|
@@ -490,7 +494,7 @@ button.list-head { cursor: pointer; }
|
|
|
490
494
|
.sheet { position: absolute; left: 0; right: 0; bottom: 0; height: 88dvh; max-height: 88vh; display: flex; flex-direction: column;
|
|
491
495
|
background: var(--surface-solid); border: 1px solid var(--hairline-strong); border-bottom: 0; border-radius: var(--r-xl) var(--r-xl) 0 0;
|
|
492
496
|
box-shadow: 0 -20px 60px rgb(0 0 0 / .45); transform: translateY(24px); opacity: 0;
|
|
493
|
-
transition: transform .22s var(--ease), opacity .22s var(--ease); padding-bottom:
|
|
497
|
+
transition: transform .22s var(--ease), opacity .22s var(--ease); padding-bottom: var(--safe-b); }
|
|
494
498
|
.sheet-root.open .scrim { opacity: 1; }
|
|
495
499
|
.sheet-root.open .sheet { transform: none; opacity: 1; }
|
|
496
500
|
.sheet-grab { width: 40px; height: 4px; border-radius: 4px; background: var(--hairline-strong); margin: 10px auto 2px; }
|
|
@@ -633,7 +637,7 @@ select.reason-input { height: 44px; }
|
|
|
633
637
|
.switch.on::after { transform: translateX(18px); background: var(--accent); }
|
|
634
638
|
.switch:disabled { opacity: .5; cursor: progress; }
|
|
635
639
|
.switch:focus-visible, .seg-step:focus-visible, .feat-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
636
|
-
.toast { position: fixed; left: 50%; bottom: calc(var(--nav-
|
|
640
|
+
.toast { position: fixed; left: 50%; bottom: calc(var(--nav-total) + 16px); transform: translateX(-50%); z-index: 60; display: flex; align-items: center; gap: 16px; max-width: calc(100vw - 32px);
|
|
637
641
|
padding: 12px 18px; border-radius: var(--r-md); border: 1px solid var(--hairline-strong); background: var(--surface-solid); color: var(--text-strong); font-size: 14px; box-shadow: 0 8px 30px rgb(0 0 0 / .35); }
|
|
638
642
|
/* ── Plans (work.md: Projects + Initiatives + Blockers) ───────────────── */
|
|
639
643
|
.fact.unknown { border-style: dashed; border-color: var(--unknown-line); } .fact.unknown .fact-n { color: var(--unknown); }
|
|
@@ -822,7 +826,7 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
|
|
|
822
826
|
.agents-page .waiting-card { margin-bottom: var(--s-3); }
|
|
823
827
|
.waiting-card.bad { border-color: var(--bad-line); background: var(--bad-wash); }
|
|
824
828
|
.need-card.muted-card { border-color: var(--unknown-line); border-style: dashed; background: var(--surface); margin-top: var(--s-4); }
|
|
825
|
-
.page-primary { position: sticky; bottom: calc(var(--nav-
|
|
829
|
+
.page-primary { position: sticky; bottom: calc(var(--nav-total) + 12px); display: flex; justify-content: center; margin-top: var(--s-4); z-index: 5; }
|
|
826
830
|
@media (min-width: 900px) { .page-primary { position: static; justify-content: flex-start; } }
|
|
827
831
|
.connect-btn { box-shadow: var(--accent-glow); }
|
|
828
832
|
.select-input { height: 44px; appearance: auto; }
|
|
@@ -943,7 +947,7 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
|
|
|
943
947
|
.hljs-comment, .hljs-quote { color: var(--text-faint); font-style: italic; }
|
|
944
948
|
.hljs-number, .hljs-literal { color: #b48ead; }
|
|
945
949
|
.hljs-title, .hljs-section, .hljs-name { color: var(--text-strong); font-weight: 600; }
|
|
946
|
-
.files-edit-fab { position: fixed; right: var(--s-4); bottom: calc(var(--nav-
|
|
950
|
+
.files-edit-fab { position: fixed; right: var(--s-4); bottom: calc(var(--nav-total) + 16px); z-index: 6; min-height: 48px; padding: 0 var(--s-6); border-radius: 999px; box-shadow: 0 8px 24px rgb(0 0 0 / .35); width: auto; }
|
|
947
951
|
.files-editor { width: 100%; min-height: calc(100vh - 220px); padding: var(--s-3); font-family: var(--font-mono); font-size: 16px; line-height: 1.5; color: var(--text-strong); background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md); white-space: pre-wrap; overflow-wrap: anywhere; resize: vertical; }
|
|
948
952
|
.files-kept { margin-bottom: var(--s-2); }
|
|
949
953
|
.files-menu, .files-conflict { display: flex; flex-direction: column; align-items: stretch; gap: var(--s-2); }
|
|
@@ -976,6 +980,8 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
|
|
|
976
980
|
.stale-banner { margin: var(--s-2) 0; padding: var(--s-2) var(--s-3); border: 1px dashed var(--unknown-line); border-radius: var(--r-md); color: var(--text-muted); font-size: 14px; }
|
|
977
981
|
.machine-line { margin: var(--s-1) 0; padding: var(--s-2) var(--s-3); border: 1px solid var(--hairline); border-radius: var(--r-md); font-size: 14.5px; color: var(--text); overflow-wrap: anywhere; }
|
|
978
982
|
.machine-line.dashed { border-style: dashed; border-color: var(--unknown-line); color: var(--text-muted); }
|
|
983
|
+
a.machine-line-link { display: block; text-decoration: none; }
|
|
984
|
+
a.machine-line-link:hover { border-color: var(--accent); color: var(--text); }
|
|
979
985
|
.sess-pick { margin-top: var(--s-10); text-align: center; }
|
|
980
986
|
.sess-open { display: flex; flex-direction: column; gap: var(--s-2); min-height: calc(100dvh - 140px); }
|
|
981
987
|
.sess-bar { display: flex; align-items: center; gap: var(--s-2); }
|
|
@@ -1010,7 +1016,9 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
|
|
|
1010
1016
|
.sess-h-tool { margin: 0; font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
1011
1017
|
.sess-pre { margin: 0; font-family: var(--font-mono); font-size: 12px; line-height: 1.4; color: #d6dbe4; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1012
1018
|
.sess-resume { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); z-index: 2; }
|
|
1013
|
-
|
|
1019
|
+
/* Phone: the bottom nav is fixed over the last --nav-h of the screen, so the
|
|
1020
|
+
composer sticks just above it, never underneath. Desktop resets it to static. */
|
|
1021
|
+
.sess-composer { display: flex; gap: var(--s-2); align-items: flex-end; position: sticky; bottom: var(--nav-total); z-index: 5; padding: var(--s-2) 0; background: var(--bg); }
|
|
1014
1022
|
.sess-input { flex: 1; min-height: 44px; font-size: 16px; resize: none; }
|
|
1015
1023
|
.sess-input::placeholder { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1016
1024
|
.sess-composer-btns { display: flex; gap: var(--s-2); }
|
|
@@ -1045,7 +1053,9 @@ button.run-mark:focus-visible { outline: 2px solid var(--accent); outline-offset
|
|
|
1045
1053
|
.sess-default-box { border: 1px solid var(--hairline); border-radius: var(--r-md); padding: var(--s-3); margin: var(--s-2) 0; }
|
|
1046
1054
|
.skeleton-row { pointer-events: none; }
|
|
1047
1055
|
.sess-view, .sess-open, .sess-term-wrap, .sess-composer { min-width: 0; max-width: 100%; }
|
|
1048
|
-
.sess-view
|
|
1056
|
+
/* clip, not hidden: hidden makes .sess-view a scroll container, which pins the
|
|
1057
|
+
sticky composer to a box that never scrolls (it then sat under the bottom nav). */
|
|
1058
|
+
.sess-view { overflow-x: clip; }
|
|
1049
1059
|
.sess-input { min-width: 0; }
|
|
1050
1060
|
.sess-term .xterm { max-width: 100%; }
|
|
1051
1061
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
failure, buildNeeds, buildDoing, buildPromised, buildRunning, buildAllowed, headline, ago, combineNeeds, machineNames,
|
|
10
10
|
} from './home-model.js';
|
|
11
11
|
import { GROUPS, legacyHref, icon, tabHref } from './nav-model.js';
|
|
12
|
+
import { fillPartial } from './home-model.js';
|
|
12
13
|
import { renderPromises, topicLink } from './promises.js';
|
|
13
14
|
import { renderMachines } from './machines.js';
|
|
14
15
|
import { renderAccounts } from './accounts.js';
|
|
@@ -29,7 +30,7 @@ import { renderSessions } from './sessions.js';
|
|
|
29
30
|
import { renderMaturation, maturationTile } from './maturation-board.js';
|
|
30
31
|
import { renderIdeas, ideasTile } from './ideas.js';
|
|
31
32
|
import { paceCard } from './pace.js';
|
|
32
|
-
import { sheetIsOpen, closeSheet, unlockCheck } from './ui.js';
|
|
33
|
+
import { sheetIsOpen, closeSheet, unlockCheck, machineIssue, MACHINES_PAGE } from './ui.js';
|
|
33
34
|
|
|
34
35
|
const TOKEN_KEY = 'instar_token'; // shared with the legacy dashboard
|
|
35
36
|
const THEME_KEY = 'instar_console_theme';
|
|
@@ -419,8 +420,11 @@ function paintHeadline(models) {
|
|
|
419
420
|
}
|
|
420
421
|
const statusNote = $('#homeStatusNote');
|
|
421
422
|
if (statusNote) {
|
|
422
|
-
|
|
423
|
-
statusNote.
|
|
423
|
+
const noteText = models.needs?.statusNote || '';
|
|
424
|
+
statusNote.replaceChildren(document.createTextNode(noteText));
|
|
425
|
+
// A machine that did not answer: link to where its state is shown.
|
|
426
|
+
if (noteText && machineIssue(noteText)) statusNote.append(' ', el('a', { href: MACHINES_PAGE, text: 'See machines' }));
|
|
427
|
+
statusNote.hidden = !noteText;
|
|
424
428
|
}
|
|
425
429
|
const issueLink = $('#heroIssueLink');
|
|
426
430
|
if (issueLink) {
|
|
@@ -430,6 +434,20 @@ function paintHeadline(models) {
|
|
|
430
434
|
$('#heroTime').textContent = `${h.tone === 'attn' ? 'What needs you' : 'Updated'} · ${new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}`;
|
|
431
435
|
}
|
|
432
436
|
|
|
437
|
+
// A machine that misses one pooled read (slow for a moment, or restarting)
|
|
438
|
+
// should not turn Home into "part could not be checked". While it is retried,
|
|
439
|
+
// its rows from the last complete answer stand in, for up to two minutes; the
|
|
440
|
+
// machines that did answer always show their fresh rows.
|
|
441
|
+
const lastComplete = {};
|
|
442
|
+
const ROW_LISTS = { attention: 'items', sessions: 'sessions' };
|
|
443
|
+
function rememberComplete(key, result) {
|
|
444
|
+
if (ROW_LISTS[key]) lastComplete[key] = { result, at: Date.now() };
|
|
445
|
+
}
|
|
446
|
+
function fillFromLastComplete(key, fresh) {
|
|
447
|
+
const prev = lastComplete[key];
|
|
448
|
+
return ROW_LISTS[key] && prev ? fillPartial(fresh, prev.result, ROW_LISTS[key], Date.now(), prev.at) : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
433
451
|
function loadHome() {
|
|
434
452
|
const run = homeRun;
|
|
435
453
|
if (!run || !run.active || run.document !== document) return;
|
|
@@ -475,7 +493,9 @@ function loadHome() {
|
|
|
475
493
|
const partial = result.ok && (['attention', 'sessions'].includes(key) && Array.isArray(result.data?.pool?.failed) && result.data.pool.failed.length > 0
|
|
476
494
|
|| key === 'topics' && ((result.data?.pool?.failed?.length || 0) > 0 || (result.data?.pool?.unresolved?.length || 0) > 0));
|
|
477
495
|
const retryable = partial || (!result.ok && !['not signed in', 'switched off on this agent'].includes(result.reason));
|
|
478
|
-
|
|
496
|
+
const filled = partial ? fillFromLastComplete(key, result) : null;
|
|
497
|
+
if (result.ok && !partial) rememberComplete(key, result);
|
|
498
|
+
results[key] = filled || (partial ? { ...result, retrying: true } : result);
|
|
479
499
|
if (key === 'systemTopics' && result.ok) {
|
|
480
500
|
const t = Number(result.data?.topics?.attention);
|
|
481
501
|
attentionTopicId = Number.isFinite(t) && t > 1 ? t : null;
|
|
@@ -496,7 +516,9 @@ function loadHome() {
|
|
|
496
516
|
// Every source has one in-flight request and its own bounded retry timer.
|
|
497
517
|
// A slow source cannot block another card or start overlapping refreshes.
|
|
498
518
|
read('pool', '/pool', POOL_TIMEOUT_MS);
|
|
499
|
-
|
|
519
|
+
// Open items only: a machine's full history (closed items included) can be
|
|
520
|
+
// over a megabyte, enough to miss the per-machine time limit.
|
|
521
|
+
read('attention', '/attention?scope=pool&status=OPEN', POOL_TIMEOUT_MS);
|
|
500
522
|
read('systemTopics', '/telegram/system-topics');
|
|
501
523
|
read('sessions', '/sessions?scope=pool', POOL_TIMEOUT_MS);
|
|
502
524
|
read('topics', '/telegram/topics?scope=pool', POOL_TIMEOUT_MS);
|
|
@@ -573,3 +573,23 @@ export function headline(allCards) {
|
|
|
573
573
|
if (bad || otherAttn || blind) return { text: 'Nothing needs you.', tone: 'ok', sub: 'I’m handling the rest. It’s under “See agent status”.' };
|
|
574
574
|
return { text: 'All quiet.', tone: 'ok', sub: 'Nothing needs you right now.' };
|
|
575
575
|
}
|
|
576
|
+
|
|
577
|
+
// ── Partial pooled reads ──────────────────────────────────────────────────
|
|
578
|
+
/** How long a machine's last complete rows may stand in while it is retried. */
|
|
579
|
+
export const PARTIAL_GRACE_MS = 2 * 60 * 1000;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Merge a partial pooled answer (some machines did not answer) with the last
|
|
583
|
+
* complete one: fresh rows from the machines that answered, plus the missing
|
|
584
|
+
* machines' rows from the last complete answer, if it is recent. Returns null
|
|
585
|
+
* when that is not possible, so the partial answer is shown as it is.
|
|
586
|
+
*/
|
|
587
|
+
export function fillPartial(fresh, previous, listKey, now, at) {
|
|
588
|
+
const failed = fresh?.data?.pool?.failed;
|
|
589
|
+
if (!previous || !Array.isArray(failed) || !failed.length || now - at > PARTIAL_GRACE_MS) return null;
|
|
590
|
+
const missing = new Set(failed.map((f) => f && f.machineId).filter(Boolean));
|
|
591
|
+
if (missing.size !== failed.length) return null; // a failure we cannot attribute to a machine stays visible
|
|
592
|
+
const rows = (previous.data?.[listKey] || []).filter((r) => r && missing.has(r.machineId));
|
|
593
|
+
return { ...fresh, retrying: true, filledFrom: at,
|
|
594
|
+
data: { ...fresh.data, [listKey]: [...(fresh.data?.[listKey] || []), ...rows], pool: { ...fresh.data.pool, failed: [] } } };
|
|
595
|
+
}
|
|
@@ -44,9 +44,6 @@ export const GROUPS = [
|
|
|
44
44
|
{ id: 'jobs', label: 'Schedule', icon: 'clock', desc: 'What runs on its own, and whether it actually ran: late, failing and running-long jobs first.' },
|
|
45
45
|
{ id: 'commitments', label: 'Promises', icon: 'check', desc: 'Every promise I made, what it is waiting on, and whether it was kept.' },
|
|
46
46
|
{ id: 'projects', label: 'Plans', icon: 'layers', desc: 'Longer work and multi-round plans: how each is going, what needs your OK, and what is in the way.' },
|
|
47
|
-
{ id: 'initiatives', label: 'Initiatives', icon: 'flag', desc: 'Now inside Plans: longer efforts in flight, and the items that need you.' },
|
|
48
|
-
{ id: 'blockers', label: 'Blockers', icon: 'alert', desc: 'Now inside Plans, under “In the way”: what looked stuck, and how sure that still is.' },
|
|
49
|
-
{ id: 'dropzone', label: 'Send content', icon: 'send', desc: 'Paste text or files straight into a session.' },
|
|
50
47
|
],
|
|
51
48
|
},
|
|
52
49
|
{
|
|
@@ -58,7 +55,6 @@ export const GROUPS = [
|
|
|
58
55
|
{ id: 'subscriptions', label: 'Accounts', icon: 'key', desc: 'Subscriptions and sign-ins: usage left before reset, and fixing a sign-in from your phone.' },
|
|
59
56
|
{ id: 'resources', label: 'Resource usage', icon: 'cpu', desc: 'CPU and memory for the server and every session.' },
|
|
60
57
|
{ id: 'spend', label: 'What thinking costs', icon: 'wallet', desc: 'Recorded AI cost on this machine: pay-per-use by model, background checks, what your conversations process, and the spending limits.' },
|
|
61
|
-
{ id: 'tokens', label: 'Tokens', icon: 'coins', desc: 'Now inside What thinking costs: how much text your conversations process, Claude and Codex kept apart.' },
|
|
62
58
|
],
|
|
63
59
|
},
|
|
64
60
|
{
|
|
@@ -66,7 +62,7 @@ export const GROUPS = [
|
|
|
66
62
|
question: 'Who is talking to whom, and with what permission?',
|
|
67
63
|
tabs: [
|
|
68
64
|
{ id: 'threadline', label: 'Other agents', icon: 'chat', desc: 'Who is waiting on you, whether each agent I work with is actually there, pairing new ones, and our conversations.' },
|
|
69
|
-
{ id: 'origins', label: 'Messages to you', icon: 'chat', desc: '
|
|
65
|
+
{ id: 'origins', label: 'Messages to you', icon: 'chat', desc: 'Who wrote each message I sent you, and the who-wrote-this footer.' },
|
|
70
66
|
{ id: 'mandates', label: 'Permission slips', icon: 'stamp', desc: 'What you have let agents do together, who is waiting for your approval, and every allow and refusal.' },
|
|
71
67
|
{ id: 'phones', label: 'Phones', icon: 'phone', desc: 'Pair the Instar iPhone app (the code shows here after a PIN unlock when there is no Telegram), and the phones already paired.' },
|
|
72
68
|
{ id: 'secrets', label: 'Passwords & keys', icon: 'lock', desc: 'Whether the agent is waiting on a password, a safe one-time link to hand one over, and the keys it already holds.' },
|
|
@@ -82,21 +78,21 @@ export const GROUPS = [
|
|
|
82
78
|
],
|
|
83
79
|
hood: [
|
|
84
80
|
{ id: 'insights', label: 'Insights', desc: 'A plain-language read of what stands out.' },
|
|
85
|
-
{ id: 'pr-pipeline', label: 'PR pipeline', desc: 'Now inside Development (Shipping): pull requests on their way to merge, and which need you.' },
|
|
86
|
-
{ id: 'evidence', label: 'Evidence', desc: 'Now inside How I’m doing inside: look up why I believe something, and what else relies on the same source.' },
|
|
87
|
-
{ id: 'routing-map', label: 'Routing map', desc: 'Now inside What thinking costs: which model handles each kind of background work, and the backups.' },
|
|
88
|
-
{ id: 'llm-activity', label: 'LLM activity', desc: 'Now inside What thinking costs: what the automatic AI checks did, failed or skipped.' },
|
|
89
|
-
{ id: 'throughput', label: 'Throughput', desc: 'Now inside Development (Pace): how much merged, how fast, and how much was redone.' },
|
|
90
81
|
{ id: 'integrated-being', label: 'How I’m doing inside', desc: 'Conversation history, recurring problems, checks on my claims.' },
|
|
91
|
-
{ id: 'process-health', label: 'Process health', desc: 'Now inside How I’m doing inside: problems I caught in my own work, and what keeps coming back.' },
|
|
92
|
-
{ id: 'feedback-drain', label: 'Feedback drain', desc: 'Now inside Development (Feedback): the approvals it needs from you, and feedback being turned into fixes.' },
|
|
93
82
|
],
|
|
94
83
|
},
|
|
95
84
|
];
|
|
96
85
|
|
|
97
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Legacy tabs whose content now lives inside another console page. They get
|
|
88
|
+
* no tile of their own (a tile that only says "now inside X" is clutter);
|
|
89
|
+
* their old links still land on the page that holds them (NATIVE_PAGES).
|
|
90
|
+
*/
|
|
91
|
+
export const MERGED = ['dropzone', 'initiatives', 'blockers', 'tokens', 'pr-pipeline', 'evidence', 'routing-map', 'llm-activity', 'throughput', 'process-health', 'feedback-drain'];
|
|
92
|
+
|
|
93
|
+
/** Every legacy tab id the console links to, including the merged ones. */
|
|
98
94
|
export function allTabIds() {
|
|
99
|
-
return GROUPS.flatMap((g) => [...g.tabs, ...(g.hood || [])].map((t) => t.id));
|
|
95
|
+
return [...GROUPS.flatMap((g) => [...g.tabs, ...(g.hood || [])].map((t) => t.id)), ...MERGED];
|
|
100
96
|
}
|
|
101
97
|
|
|
102
98
|
/** Features rebuilt natively in the console, by legacy tab id. */
|
|
@@ -104,6 +100,8 @@ export const NATIVE_PAGES = {
|
|
|
104
100
|
commitments: '#/work/promises',
|
|
105
101
|
jobs: '#/work/schedule',
|
|
106
102
|
sessions: '#/work/sessions',
|
|
103
|
+
// Sending text or files now happens in each session's own message box.
|
|
104
|
+
dropzone: '#/work/sessions',
|
|
107
105
|
projects: '#/work/plans',
|
|
108
106
|
initiatives: '#/work/plans',
|
|
109
107
|
blockers: '#/work/plans?s=in-the-way',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import {
|
|
11
11
|
buildSchedulePage, buildHistory, markText, storySentence, storyLines, jobActions, toggleConfirm, runStartWords, runFollowWords, saveRefusalWords, originWords, scheduleWords, ago, HISTORY_LIMIT, STRIP_LIMIT,
|
|
12
12
|
} from './schedule-model.js';
|
|
13
|
-
import { el, setKids, pageHead, section, sheet, closeSheet, sheetIsOpen, story, details, note, cantTell } from './ui.js';
|
|
13
|
+
import { el, setKids, pageHead, section, sheet, closeSheet, sheetIsOpen, story, details, note, noteLinked, cantTell } from './ui.js';
|
|
14
14
|
import { legacyHref } from './nav-model.js';
|
|
15
15
|
import { verifiedDisplayName } from './display-identity.js';
|
|
16
16
|
|
|
@@ -73,7 +73,7 @@ function paint() {
|
|
|
73
73
|
setKids(main, el('div', { class: 'schedule-page' },
|
|
74
74
|
pageHead({ eyebrow: EYEBROW, headline: page.headline, sub: page.sub, tone: page.tone, facts: page.facts }),
|
|
75
75
|
liveNote(),
|
|
76
|
-
page.notes.map((t) =>
|
|
76
|
+
page.notes.map((t) => noteLinked(t)),
|
|
77
77
|
el('div', { class: 'page-sections' }, needs, coming, every, setupSection(page.setup, refresh)),
|
|
78
78
|
legacyLink(),
|
|
79
79
|
));
|
|
@@ -150,7 +150,7 @@ function setupSection(setup, refresh) {
|
|
|
150
150
|
}
|
|
151
151
|
body.append(row);
|
|
152
152
|
}
|
|
153
|
-
for (const t of setup.notes) body.append(
|
|
153
|
+
for (const t of setup.notes) body.append(noteLinked(t));
|
|
154
154
|
wrap.append(head, body);
|
|
155
155
|
return wrap;
|
|
156
156
|
}
|
|
@@ -215,7 +215,7 @@ function historyView(h) {
|
|
|
215
215
|
strip.append(b);
|
|
216
216
|
}
|
|
217
217
|
const wrap = el('div', { class: 'history' }, strip, detail);
|
|
218
|
-
for (const t of h.notes) wrap.append(
|
|
218
|
+
for (const t of h.notes) wrap.append(noteLinked(t));
|
|
219
219
|
{
|
|
220
220
|
const list = el('ol', { class: 'run-list', hidden: true });
|
|
221
221
|
let day = null;
|
|
@@ -131,6 +131,24 @@ export function historyUrl(s, before = null) {
|
|
|
131
131
|
return `/sessions/${encodeURIComponent(s.tmuxSession)}/transcript?${q}`;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/** After a session's own record runs out, History continues into its topic's earlier
|
|
135
|
+
* messages (earlier sessions of the same conversation), a page at a time. */
|
|
136
|
+
export const TOPIC_HISTORY_PAGE = 50;
|
|
137
|
+
export function topicOf(s) {
|
|
138
|
+
const n = Number(s && s.platform === 'telegram' ? s.platformId : NaN);
|
|
139
|
+
return Number.isSafeInteger(n) && n > 1 ? n : null;
|
|
140
|
+
}
|
|
141
|
+
export function topicHistoryUrl(topicId, beforeMs) {
|
|
142
|
+
const q = new URLSearchParams({ limit: String(TOPIC_HISTORY_PAGE), scope: 'pool', before: String(Math.floor(beforeMs)) });
|
|
143
|
+
return `/telegram/topics/${encodeURIComponent(topicId)}/messages?${q}`;
|
|
144
|
+
}
|
|
145
|
+
/** A topic message as a History entry: who, when, what. */
|
|
146
|
+
export function topicHistoryEntry(m) {
|
|
147
|
+
if (!m || typeof m.text !== 'string' || !m.text.trim()) return null;
|
|
148
|
+
const at = Date.parse(m.timestamp);
|
|
149
|
+
return { role: m.fromUser ? 'user' : 'assistant', text: m.text, at: Number.isFinite(at) ? m.timestamp : null, atMs: Number.isFinite(at) ? at : null };
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
/** Words for a failed History read: never "no history" when the read failed. */
|
|
135
153
|
export function historyErrorWords(res) {
|
|
136
154
|
const d = (res && res.data) || {};
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
buildSessions, sessionsHeadline, sessionsFacts, partialWords, titleLine, closeRequest, closeConfirmWords,
|
|
12
12
|
closeResultWords, doorChoices, modelChoices, doorModelWords, regimeWords, machineChoices, PLATFORMS,
|
|
13
13
|
platformAllowsDoor, createBody, createResultWords, defaultWords, profileSourceWords, switchLanded,
|
|
14
|
-
canSwitchDoor, attachmentMessage, historyUrl, historyErrorWords, historyText, KEYS, STOP_KEY, frame, nextHistoryBatch, streamErrorWords, composerRows, modelWords, HISTORY_FIRST,
|
|
14
|
+
canSwitchDoor, attachmentMessage, historyUrl, historyErrorWords, historyText, topicOf, topicHistoryUrl, topicHistoryEntry, TOPIC_HISTORY_PAGE, KEYS, STOP_KEY, frame, nextHistoryBatch, streamErrorWords, composerRows, modelWords, HISTORY_FIRST,
|
|
15
15
|
} from './sessions-model.js';
|
|
16
|
-
import { el, setKids, pageHead, sheet, closeSheet, story, details, note, cantTell } from './ui.js';
|
|
16
|
+
import { el, setKids, pageHead, sheet, closeSheet, story, details, note, noteLinked, cantTell, MACHINES_PAGE } from './ui.js';
|
|
17
17
|
import { legacyHref } from './nav-model.js';
|
|
18
18
|
import { topicNameMap } from './topic-names.js';
|
|
19
19
|
|
|
@@ -122,13 +122,14 @@ function paintList(box, m, openKey) {
|
|
|
122
122
|
parts.push(cantTell(m.reason));
|
|
123
123
|
} else {
|
|
124
124
|
const pw = partialWords(m);
|
|
125
|
-
|
|
125
|
+
// A machine that did not answer links to the Machines page, where its state and reachability are shown.
|
|
126
|
+
if (pw) parts.push(noteLinked(pw));
|
|
126
127
|
if (!m.rows.length) parts.push(el('p', { class: 'empty-line', text: m.multi ? 'Nothing is running on any machine.' : 'Nothing is running on this machine.' }));
|
|
127
128
|
if (m.needsLook.length) parts.push(group('Needs a look', 'look', m.needsLook, openKey));
|
|
128
129
|
if (m.working.length) parts.push(group('Working', 'working', m.working, openKey));
|
|
129
130
|
if (m.unanswered.length || m.idleMachines.length) {
|
|
130
131
|
parts.push(el('section', { class: 'list-section', id: 'sec-idle' }, el('div', { class: 'list-head' }, el('h2', { class: 'eyebrow', text: 'Other machines' })),
|
|
131
|
-
m.unanswered.map((u) => el('
|
|
132
|
+
m.unanswered.map((u) => el('a', { class: 'machine-line dashed machine-line-link', href: MACHINES_PAGE, text: `${u.name}: ${u.words} ›` })),
|
|
132
133
|
m.idleMachines.map((i) => el('p', { class: `machine-line${i.ok ? '' : ' dashed'}`, text: `${i.name}: ${i.words}` }))));
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -434,11 +435,20 @@ function sendKey(key) {
|
|
|
434
435
|
|
|
435
436
|
// ── History: the conversation record, newest at the bottom, older on scroll-up ──
|
|
436
437
|
async function openHistory(view) {
|
|
437
|
-
view.history = { before: null, done: false, loading: false, count: 0 };
|
|
438
|
+
view.history = { before: null, done: false, loading: false, count: 0, phase: 'session', oldestMs: null, topicBefore: null };
|
|
438
439
|
view.historyBox.addEventListener('scroll', () => { if (view.historyBox.scrollTop < 200) loadHistory(view); });
|
|
439
440
|
await loadHistory(view);
|
|
440
441
|
}
|
|
441
442
|
|
|
443
|
+
function historyRow(e) {
|
|
444
|
+
const text = historyText(e);
|
|
445
|
+
if (!text) return null;
|
|
446
|
+
const when = e.at ? new Date(e.at).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : null;
|
|
447
|
+
return e.role === 'tool'
|
|
448
|
+
? el('p', { class: 'sess-h sess-h-tool', text: `→ ${text}` })
|
|
449
|
+
: el('div', { class: `sess-h sess-h-${e.role}` }, el('div', { class: 'sess-h-who', text: [e.role === 'user' ? 'Message in' : 'Session', when].filter(Boolean).join(' · ') }), el('div', { class: 'sess-h-text', text }));
|
|
450
|
+
}
|
|
451
|
+
|
|
442
452
|
async function loadHistory(view) {
|
|
443
453
|
const h = view.history;
|
|
444
454
|
if (!h || h.loading || h.done || st.view !== view) return;
|
|
@@ -446,33 +456,59 @@ async function loadHistory(view) {
|
|
|
446
456
|
const box = view.historyBox;
|
|
447
457
|
const marker = el('p', { class: 'sess-history-note', text: h.count ? 'Loading earlier messages…' : 'Loading the conversation…' });
|
|
448
458
|
box.prepend(marker);
|
|
449
|
-
const
|
|
459
|
+
const topicPhase = h.phase === 'topic';
|
|
460
|
+
const res = await send('GET', topicPhase ? topicHistoryUrl(topicOf(view.s), h.topicBefore) : historyUrl(view.s, h.before));
|
|
450
461
|
marker.remove();
|
|
451
462
|
h.loading = false;
|
|
452
463
|
if (st.view !== view) return;
|
|
453
|
-
|
|
464
|
+
const list = topicPhase ? res.data && res.data.messages : res.data && res.data.entries;
|
|
465
|
+
if (!res.ok || !Array.isArray(list)) {
|
|
454
466
|
h.done = true;
|
|
455
|
-
box.prepend(el('p', { class: 'sess-history-note', text: historyErrorWords(res) }));
|
|
467
|
+
box.prepend(el('p', { class: 'sess-history-note', text: topicPhase ? `Can’t load earlier messages: ${res.reason || 'the server did not answer'}.` : historyErrorWords(res) }));
|
|
456
468
|
return;
|
|
457
469
|
}
|
|
458
470
|
const first = !h.count;
|
|
459
471
|
const prevHeight = box.scrollHeight, prevTop = box.scrollTop;
|
|
460
472
|
const frag = document.createDocumentFragment();
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
473
|
+
// A server without scroll-back support ignores `before` and returns the newest
|
|
474
|
+
// messages: keep only what is strictly older than the cursor, so nothing repeats.
|
|
475
|
+
const entries = topicPhase
|
|
476
|
+
? list.map(topicHistoryEntry).filter((e) => e && e.atMs != null && e.atMs < h.topicBefore)
|
|
477
|
+
: list;
|
|
478
|
+
for (const e of entries) {
|
|
479
|
+
const row = historyRow(e);
|
|
480
|
+
if (row) frag.append(row);
|
|
481
|
+
const ms = e.atMs ?? (e.at ? Date.parse(e.at) : NaN);
|
|
482
|
+
if (Number.isFinite(ms)) h.oldestMs = h.oldestMs == null ? ms : Math.min(h.oldestMs, ms);
|
|
483
|
+
}
|
|
484
|
+
h.count += entries.length;
|
|
485
|
+
if (topicPhase) {
|
|
486
|
+
h.topicCount += entries.length;
|
|
487
|
+
// Nothing earlier after all: the divider would introduce an empty section.
|
|
488
|
+
if (!h.topicCount && h.divider) { h.divider.remove(); h.divider = null; }
|
|
489
|
+
h.topicBefore = h.oldestMs;
|
|
490
|
+
if (list.length < TOPIC_HISTORY_PAGE || entries.length === 0 || h.topicBefore == null) { h.done = true; frag.prepend(el('p', { class: 'sess-history-note', text: 'The start of this conversation.' })); }
|
|
491
|
+
} else {
|
|
492
|
+
h.before = res.data.nextBefore;
|
|
493
|
+
if (h.before == null) {
|
|
494
|
+
// This session's own record ends here. A conversation keeps going back
|
|
495
|
+
// through its earlier sessions: continue with the topic's messages.
|
|
496
|
+
if (topicOf(view.s)) {
|
|
497
|
+
h.phase = 'topic';
|
|
498
|
+
h.topicBefore = h.oldestMs ?? Date.now();
|
|
499
|
+
h.divider = el('p', { class: 'sess-history-note', text: 'Earlier, from this conversation’s previous sessions:' });
|
|
500
|
+
h.topicCount = 0;
|
|
501
|
+
frag.prepend(h.divider);
|
|
502
|
+
} else {
|
|
503
|
+
h.done = true;
|
|
504
|
+
frag.prepend(el('p', { class: 'sess-history-note', text: h.count ? 'The start of this conversation.' : 'Nothing has been said in this session yet.' }));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
468
507
|
}
|
|
469
|
-
h.count += res.data.entries.length;
|
|
470
|
-
h.before = res.data.nextBefore;
|
|
471
|
-
if (h.before == null) { h.done = true; frag.prepend(el('p', { class: 'sess-history-note', text: h.count ? 'The start of this conversation.' : 'Nothing has been said in this session yet.' })); }
|
|
472
508
|
box.prepend(frag);
|
|
473
509
|
if (first) box.scrollTop = box.scrollHeight;
|
|
474
510
|
else box.scrollTop = prevTop + (box.scrollHeight - prevHeight);
|
|
475
|
-
// A short
|
|
511
|
+
// A short page may not fill the box: keep loading until it scrolls.
|
|
476
512
|
if (!h.done && box.scrollHeight <= box.clientHeight + 200) loadHistory(view);
|
|
477
513
|
}
|
|
478
514
|
|
package/dashboard/console/ui.js
CHANGED
|
@@ -32,6 +32,17 @@ export function pageHead({ eyebrow, headline, sub, tone, facts = [] }) {
|
|
|
32
32
|
/** A dashed honesty note (can't tell / partial / stale). */
|
|
33
33
|
export function note(text) { return el('p', { class: 'card-note', text }); }
|
|
34
34
|
|
|
35
|
+
/** Where a machine's reachability is shown. */
|
|
36
|
+
export const MACHINES_PAGE = '#/capacity/machines';
|
|
37
|
+
/** A note about a machine that did not answer: anything that fails links to the problem. */
|
|
38
|
+
export function machineIssue(text) {
|
|
39
|
+
return /did not answer|not answering|didn[’']t answer|Couldn['’]t read other machines/i.test(String(text || ''));
|
|
40
|
+
}
|
|
41
|
+
export function noteLinked(text) {
|
|
42
|
+
if (!machineIssue(text)) return note(text);
|
|
43
|
+
return el('p', { class: 'card-note' }, document.createTextNode(`${text} `), el('a', { href: MACHINES_PAGE, text: 'See machines' }));
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
/** 5. Can't tell, as a whole page. */
|
|
36
47
|
export function cantTell(reason) {
|
|
37
48
|
return el('section', { class: 'card', 'data-state': 'unknown' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -7833,6 +7833,7 @@ export async function startServer(options) {
|
|
|
7833
7833
|
// Send-only still owns the attention store: run its hourly lifecycle
|
|
7834
7834
|
// pass here too (start() is never called on this path). Idempotent.
|
|
7835
7835
|
telegram.startSendOnlyMaintenance();
|
|
7836
|
+
telegram.setMachineNameResolver((machineId) => _listPoolMachines?.().find((m) => m.machineId === machineId)?.nickname ?? null);
|
|
7836
7837
|
// Resolve any topic names still using the fallback "topic-NNNN" pattern
|
|
7837
7838
|
telegram.resolveUnknownTopicNames().catch(err => {
|
|
7838
7839
|
console.warn(`[telegram] Topic name resolution failed: ${err}`);
|
|
@@ -7943,6 +7944,7 @@ export async function startServer(options) {
|
|
|
7943
7944
|
// When the user renames a Telegram forum topic, update the bound
|
|
7944
7945
|
// session's display `name` ONLY (never tmuxSession or id). Fire-and-
|
|
7945
7946
|
// forget; rename-display is non-critical.
|
|
7947
|
+
telegram.setMachineNameResolver((machineId) => _listPoolMachines?.().find((m) => m.machineId === machineId)?.nickname ?? null);
|
|
7946
7948
|
telegram.setTopicRenamedHandler((topicId, newName) => {
|
|
7947
7949
|
const tmuxSession = telegram?.getSessionForTopic(topicId);
|
|
7948
7950
|
if (!tmuxSession)
|
|
@@ -8615,7 +8617,7 @@ export async function startServer(options) {
|
|
|
8615
8617
|
// its request store with the Telegram chat route).
|
|
8616
8618
|
if (_appNativeOn) {
|
|
8617
8619
|
try {
|
|
8618
|
-
const [{ AppConversationService }, { createAppInboundDispatch }, { MobileChatRequestStore }, { appOwnerName }, relayMod, replMod] = await Promise.all([
|
|
8620
|
+
const [{ AppConversationService, VOICE_TRANSCRIBE_DEADLINE_MS }, { createAppInboundDispatch }, { MobileChatRequestStore }, { appOwnerName }, relayMod, replMod] = await Promise.all([
|
|
8619
8621
|
import('../core/AppConversationService.js'), import('../core/appInboundDispatch.js'), import('../core/MobileChatRequests.js'),
|
|
8620
8622
|
import('../server/mobileConversationRoutes.js'), import('../core/AppReplyRelayInstaller.js'), import('../core/AppConversationsReplicatedStore.js'),
|
|
8621
8623
|
]);
|
|
@@ -8629,8 +8631,9 @@ export async function startServer(options) {
|
|
|
8629
8631
|
let appDispatch = null;
|
|
8630
8632
|
// Signed records (§4.6): this machine's identity signer, the signing-key history lookup and
|
|
8631
8633
|
// the device-key ledger. No identity ⇒ messages are still stored and delivered, read `unknown`.
|
|
8632
|
-
const [sigMod, ledgerMod, ledgerReplMod] = await Promise.all([
|
|
8634
|
+
const [sigMod, ledgerMod, ledgerReplMod, voiceMod] = await Promise.all([
|
|
8633
8635
|
import('../core/AppRecordSignatures.js'), import('../core/MobileDeviceKeyLedger.js'), import('../core/MobileDeviceKeyLedgerReplicatedStore.js'),
|
|
8636
|
+
import('../core/VoiceTranscription.js'),
|
|
8634
8637
|
]);
|
|
8635
8638
|
const appSigner = sigMod.loadMachineSigner(config.stateDir);
|
|
8636
8639
|
const appKeyLookup = sigMod.identityKeyLookup(config.stateDir);
|
|
@@ -8673,6 +8676,8 @@ export async function startServer(options) {
|
|
|
8673
8676
|
catch { /* @silent-fallback-ok — the stop record is observability */ }
|
|
8674
8677
|
},
|
|
8675
8678
|
mediaMaxStoreBytes: () => config.mobilePairing?.native?.media?.maxStoreBytes,
|
|
8679
|
+
// §4.5.1: voice notes the phone did not transcribe use Telegram's transcriber (same provider).
|
|
8680
|
+
transcribeVoice: (filePath, mediaType) => voiceMod.transcribeAudioFile(filePath, { explicit: voiceMod.configuredVoiceProvider(config), mediaType, timeoutMs: VOICE_TRANSCRIBE_DEADLINE_MS }),
|
|
8676
8681
|
raiseAttention: (item) => { void telegram?.createAttentionItem({ id: item.conditionKey, conditionKey: item.conditionKey, title: item.title, summary: item.body, category: 'general', priority: 'NORMAL', sourceContext: 'app-media' }); },
|
|
8677
8682
|
clearAttention: (conditionKey) => { telegram?.clearAttentionCondition({ conditionKey }, 'cleared: the app media store has room again'); },
|
|
8678
8683
|
reportDegradation: (what, detail) => degradationReporter.report({
|
|
@@ -11829,7 +11834,7 @@ export async function startServer(options) {
|
|
|
11829
11834
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
11830
11835
|
if (slackChId && _slackAdapter) {
|
|
11831
11836
|
const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
|
|
11832
|
-
return msgs.map(m => ({ text: m.text, fromUser:
|
|
11837
|
+
return msgs.map(m => ({ text: m.text, fromUser: _slackAdapter.isMessageFromUser(m), timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
|
|
11833
11838
|
}
|
|
11834
11839
|
if (telegram) {
|
|
11835
11840
|
const entries = telegram.getTopicHistory(topicId, limit);
|
|
@@ -11909,7 +11914,7 @@ export async function startServer(options) {
|
|
|
11909
11914
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
11910
11915
|
if (slackChId && _slackAdapter) {
|
|
11911
11916
|
const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
|
|
11912
|
-
return msgs.map(m => ({ text: m.text, fromUser:
|
|
11917
|
+
return msgs.map(m => ({ text: m.text, fromUser: _slackAdapter.isMessageFromUser(m), timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
|
|
11913
11918
|
}
|
|
11914
11919
|
if (telegram) {
|
|
11915
11920
|
const entries = telegram.getTopicHistory(topicId, limit);
|
|
@@ -12389,7 +12394,7 @@ export async function startServer(options) {
|
|
|
12389
12394
|
const slackChId = slackProxyChannelMap.get(topicId);
|
|
12390
12395
|
if (slackChId && _slackAdapter) {
|
|
12391
12396
|
const msgs = _slackAdapter.getChannelMessages(slackChId, limit);
|
|
12392
|
-
return msgs.map(m => ({ text: m.text, fromUser:
|
|
12397
|
+
return msgs.map(m => ({ text: m.text, fromUser: _slackAdapter.isMessageFromUser(m), timestamp: new Date(parseFloat(m.ts) * 1000).toISOString() }));
|
|
12393
12398
|
}
|
|
12394
12399
|
if (telegram) {
|
|
12395
12400
|
const history = telegram.getMessageLog?.();
|
|
@@ -12429,6 +12434,8 @@ export async function startServer(options) {
|
|
|
12429
12434
|
// (update-train churn) forgot which dead sessions were already
|
|
12430
12435
|
// announced and re-posted the same death once per boot.
|
|
12431
12436
|
statePath: path.join(config.stateDir, 'state', 'session-monitor-ctx-notified.json'),
|
|
12437
|
+
// Same for "session has stopped": one notice per death, across restarts.
|
|
12438
|
+
deadNoticeStatePath: path.join(config.stateDir, 'state', 'session-monitor-dead-notified.json'),
|
|
12432
12439
|
// A topic an ONLINE peer actively owns is not this machine's to monitor:
|
|
12433
12440
|
// route() forwards its inbound to that owner, so a stale local mapping
|
|
12434
12441
|
// must never produce a "session has stopped" notice or a local triage
|