instar 1.3.1663 → 1.3.1665
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/dist/commands/postDriveTranscriptAudit.d.ts.map +1 -1
- package/dist/commands/postDriveTranscriptAudit.js +3 -2
- package/dist/commands/postDriveTranscriptAudit.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +65 -30
- package/dist/commands/server.js.map +1 -1
- package/dist/core/BetweenWindowAdmissionGate.d.ts.map +1 -1
- package/dist/core/BetweenWindowAdmissionGate.js +3 -2
- package/dist/core/BetweenWindowAdmissionGate.js.map +1 -1
- package/dist/core/DirectDesktopAccess.d.ts.map +1 -1
- package/dist/core/DirectDesktopAccess.js +2 -1
- package/dist/core/DirectDesktopAccess.js.map +1 -1
- package/dist/core/LiveTailSource.d.ts.map +1 -1
- package/dist/core/LiveTailSource.js +5 -48
- package/dist/core/LiveTailSource.js.map +1 -1
- package/dist/core/MeshRpc.d.ts +4 -0
- package/dist/core/MeshRpc.d.ts.map +1 -1
- package/dist/core/MeshRpc.js +4 -0
- package/dist/core/MeshRpc.js.map +1 -1
- package/dist/core/OwnershipApplier.d.ts.map +1 -1
- package/dist/core/OwnershipApplier.js +5 -4
- package/dist/core/OwnershipApplier.js.map +1 -1
- package/dist/core/OwnershipReconciler.d.ts.map +1 -1
- package/dist/core/OwnershipReconciler.js +5 -4
- package/dist/core/OwnershipReconciler.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +6 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +25 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/PrHandLease.d.ts.map +1 -1
- package/dist/core/PrHandLease.js +2 -1
- package/dist/core/PrHandLease.js.map +1 -1
- package/dist/core/SafeGitExecutor.d.ts +33 -11
- package/dist/core/SafeGitExecutor.d.ts.map +1 -1
- package/dist/core/SafeGitExecutor.js +144 -116
- package/dist/core/SafeGitExecutor.js.map +1 -1
- package/dist/core/ScopeVerifier.d.ts.map +1 -1
- package/dist/core/ScopeVerifier.js +4 -3
- package/dist/core/ScopeVerifier.js.map +1 -1
- package/dist/core/SeamlessOrchestratorEngine.d.ts.map +1 -1
- package/dist/core/SeamlessOrchestratorEngine.js +3 -31
- package/dist/core/SeamlessOrchestratorEngine.js.map +1 -1
- package/dist/core/SessionDrainRunner.d.ts.map +1 -1
- package/dist/core/SessionDrainRunner.js +4 -3
- package/dist/core/SessionDrainRunner.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +11 -10
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionRouter.d.ts +14 -0
- package/dist/core/SessionRouter.d.ts.map +1 -1
- package/dist/core/SessionRouter.js +42 -1
- package/dist/core/SessionRouter.js.map +1 -1
- package/dist/core/StaleOwnerReleaseEngine.d.ts.map +1 -1
- package/dist/core/StaleOwnerReleaseEngine.js +2 -1
- package/dist/core/StaleOwnerReleaseEngine.js.map +1 -1
- package/dist/core/TelegramLiveSender.d.ts.map +1 -1
- package/dist/core/TelegramLiveSender.js +3 -2
- package/dist/core/TelegramLiveSender.js.map +1 -1
- package/dist/core/TelegramSpendTopicChannel.d.ts.map +1 -1
- package/dist/core/TelegramSpendTopicChannel.js +2 -1
- package/dist/core/TelegramSpendTopicChannel.js.map +1 -1
- package/dist/core/TopicProfileOrchestrator.d.ts.map +1 -1
- package/dist/core/TopicProfileOrchestrator.js +2 -1
- package/dist/core/TopicProfileOrchestrator.js.map +1 -1
- package/dist/core/TopicProfileStore.d.ts.map +1 -1
- package/dist/core/TopicProfileStore.js +2 -1
- package/dist/core/TopicProfileStore.js.map +1 -1
- package/dist/core/TopicProfileTransferCarrier.d.ts.map +1 -1
- package/dist/core/TopicProfileTransferCarrier.js +2 -1
- package/dist/core/TopicProfileTransferCarrier.js.map +1 -1
- package/dist/core/conversationBindGate.d.ts +0 -21
- package/dist/core/conversationBindGate.d.ts.map +1 -1
- package/dist/core/conversationBindGate.js +23 -1
- package/dist/core/conversationBindGate.js.map +1 -1
- package/dist/core/topicHistoryPull.d.ts +50 -0
- package/dist/core/topicHistoryPull.d.ts.map +1 -0
- package/dist/core/topicHistoryPull.js +87 -0
- package/dist/core/topicHistoryPull.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/memory/TopicSummarizer.d.ts.map +1 -1
- package/dist/memory/TopicSummarizer.js +3 -2
- package/dist/memory/TopicSummarizer.js.map +1 -1
- package/dist/messaging/ColdStartFallbackReply.d.ts.map +1 -1
- package/dist/messaging/ColdStartFallbackReply.js +3 -25
- package/dist/messaging/ColdStartFallbackReply.js.map +1 -1
- package/dist/messaging/NotificationBatcher.d.ts.map +1 -1
- package/dist/messaging/NotificationBatcher.js +2 -1
- package/dist/messaging/NotificationBatcher.js.map +1 -1
- package/dist/messaging/OutboundAdvisory.d.ts.map +1 -1
- package/dist/messaging/OutboundAdvisory.js +3 -2
- package/dist/messaging/OutboundAdvisory.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +3 -2
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/shared/topicRef.d.ts +42 -0
- package/dist/messaging/shared/topicRef.d.ts.map +1 -0
- package/dist/messaging/shared/topicRef.js +82 -0
- package/dist/messaging/shared/topicRef.js.map +1 -0
- package/dist/messaging/telegram-origin/OriginForwardToHolder.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/OriginForwardToHolder.js +4 -3
- package/dist/messaging/telegram-origin/OriginForwardToHolder.js.map +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.js +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.js.map +1 -1
- package/dist/messaging/telegramTopicSpace.d.ts.map +1 -1
- package/dist/messaging/telegramTopicSpace.js +2 -1
- package/dist/messaging/telegramTopicSpace.js.map +1 -1
- package/dist/monitoring/AutonomousLivenessReconciler.d.ts.map +1 -1
- package/dist/monitoring/AutonomousLivenessReconciler.js +9 -41
- package/dist/monitoring/AutonomousLivenessReconciler.js.map +1 -1
- package/dist/monitoring/PromiseBeacon.d.ts.map +1 -1
- package/dist/monitoring/PromiseBeacon.js +3 -2
- package/dist/monitoring/PromiseBeacon.js.map +1 -1
- package/dist/monitoring/ReapNoticeDrain.d.ts.map +1 -1
- package/dist/monitoring/ReapNoticeDrain.js +2 -1
- package/dist/monitoring/ReapNoticeDrain.js.map +1 -1
- package/dist/monitoring/ReapNotifier.d.ts.map +1 -1
- package/dist/monitoring/ReapNotifier.js +3 -2
- package/dist/monitoring/ReapNotifier.js.map +1 -1
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +13 -12
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/dist/monitoring/SessionRecovery.d.ts.map +1 -1
- package/dist/monitoring/SessionRecovery.js +4 -3
- package/dist/monitoring/SessionRecovery.js.map +1 -1
- package/dist/monitoring/TopicReachabilityVerifier.d.ts.map +1 -1
- package/dist/monitoring/TopicReachabilityVerifier.js +2 -20
- package/dist/monitoring/TopicReachabilityVerifier.js.map +1 -1
- package/dist/monitoring/machineMaintenance/WorkYieldRegistry.d.ts +0 -15
- package/dist/monitoring/machineMaintenance/WorkYieldRegistry.d.ts.map +1 -1
- package/dist/monitoring/machineMaintenance/WorkYieldRegistry.js +3 -2
- package/dist/monitoring/machineMaintenance/WorkYieldRegistry.js.map +1 -1
- package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
- package/dist/monitoring/sentinelWiring.js +2 -1
- package/dist/monitoring/sentinelWiring.js.map +1 -1
- package/dist/native/InstarWedgeCanary.app/Contents/MacOS/InstarWedgeCanary +0 -0
- package/dist/native/instar-desktop-host +0 -0
- package/dist/recommended/catalog.js +1 -1
- package/dist/recommended/catalog.js.map +1 -1
- package/dist/recommended/recipes.d.ts.map +1 -1
- package/dist/recommended/recipes.js +3 -2
- package/dist/recommended/recipes.js.map +1 -1
- package/dist/recommended/setupState.d.ts +9 -0
- package/dist/recommended/setupState.d.ts.map +1 -1
- package/dist/recommended/setupState.js +17 -0
- package/dist/recommended/setupState.js.map +1 -1
- package/dist/scheduler/MentorAutonomousGuardian.d.ts.map +1 -1
- package/dist/scheduler/MentorAutonomousGuardian.js +3 -30
- package/dist/scheduler/MentorAutonomousGuardian.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +7 -1
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +10 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/mobilePairingRoutes.d.ts.map +1 -1
- package/dist/server/mobilePairingRoutes.js +4 -0
- package/dist/server/mobilePairingRoutes.js.map +1 -1
- package/dist/server/mobileProposalsInsideRoutes.d.ts +155 -0
- package/dist/server/mobileProposalsInsideRoutes.d.ts.map +1 -0
- package/dist/server/mobileProposalsInsideRoutes.js +626 -0
- package/dist/server/mobileProposalsInsideRoutes.js.map +1 -0
- package/dist/server/recommendedServiceRoutes.d.ts +2 -0
- package/dist/server/recommendedServiceRoutes.d.ts.map +1 -1
- package/dist/server/recommendedServiceRoutes.js +41 -7
- package/dist/server/recommendedServiceRoutes.js.map +1 -1
- package/dist/server/routes/remediation-proposals.d.ts +1 -0
- package/dist/server/routes/remediation-proposals.d.ts.map +1 -1
- package/dist/server/routes/remediation-proposals.js +1 -1
- package/dist/server/routes/remediation-proposals.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +75 -42
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/HubIntentClassifier.d.ts.map +1 -1
- package/dist/threadline/HubIntentClassifier.js +2 -1
- package/dist/threadline/HubIntentClassifier.js.map +1 -1
- package/dist/threadline/PairingInviteTelegram.d.ts.map +1 -1
- package/dist/threadline/PairingInviteTelegram.js +2 -1
- package/dist/threadline/PairingInviteTelegram.js.map +1 -1
- package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
- package/dist/threadline/ThreadlineRouter.js +6 -2
- package/dist/threadline/ThreadlineRouter.js.map +1 -1
- package/dist/threadline/TopicLinkageHandler.d.ts.map +1 -1
- package/dist/threadline/TopicLinkageHandler.js +8 -3
- package/dist/threadline/TopicLinkageHandler.js.map +1 -1
- package/dist/threadline/hubCommands.d.ts.map +1 -1
- package/dist/threadline/hubCommands.js +2 -1
- package/dist/threadline/hubCommands.js.map +1 -1
- package/dist/types/pipeline.d.ts.map +1 -1
- package/dist/types/pipeline.js +6 -1
- package/dist/types/pipeline.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- 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/upgrades/1.3.1664.md +32 -0
- package/upgrades/1.3.1665.md +36 -0
- package/upgrades/side-effects/mobile-native-proposals-inside.md +57 -0
- package/upgrades/side-effects/recommended-services-stop-holds.md +64 -0
- package/upgrades/side-effects/safe-git-one-verb-classifier.md +117 -0
- package/upgrades/side-effects/session-pool-keep-and-recall.md +49 -0
- package/upgrades/side-effects/topic-name-beside-id.md +152 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobileProposalsInsideRoutes.d.ts","sourceRoot":"","sources":["../../src/server/mobileProposalsInsideRoutes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { ownerOrBearer, rowKey, singleFlight, POOL_READ_CACHE_MS } from './mobileCapacityRoutes.js';
|
|
2
|
+
import { cleanText } from './mobilePlansPeopleRoutes.js';
|
|
3
|
+
import { verifiedEntityName } from '../messaging/shared/channelIdentity.js';
|
|
4
|
+
import { DegradationReporter } from '../monitoring/DegradationReporter.js';
|
|
5
|
+
export const MOBILE_PROPOSALS_INSIDE_SCHEMA_VERSION = 1;
|
|
6
|
+
export const MAX_PROPOSAL_ROWS = 30;
|
|
7
|
+
/** One-way keys a machine lists so a merged count has no duplicates, past the row cap too. */
|
|
8
|
+
export const MAX_PROPOSAL_KEYS = 500;
|
|
9
|
+
/** How many outstanding proposals a machine shows before the rest queue (the console's "visible 3"). */
|
|
10
|
+
export const VISIBLE_PROPOSALS = 3;
|
|
11
|
+
export const ACTIVITY_WINDOW = 40;
|
|
12
|
+
export const MAX_ACTIVITY_ROWS = 30;
|
|
13
|
+
export const MAX_PROBLEM_ROWS = 10;
|
|
14
|
+
export const CLAIMS_WINDOW = 20;
|
|
15
|
+
export const MAX_REVIEW_ROWS = 10;
|
|
16
|
+
const rec = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
17
|
+
const arr = (v) => (Array.isArray(v) ? v.filter((x) => x && typeof x === 'object' && !Array.isArray(x)) : []);
|
|
18
|
+
const ts = (v) => { const t = typeof v === 'number' ? v : Date.parse(String(v ?? '')); return Number.isFinite(t) && t > 0 ? t : 0; };
|
|
19
|
+
const iso = (t) => (t > 0 ? new Date(t).toISOString() : null);
|
|
20
|
+
const count = (v) => { const n = Number(v); return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0; };
|
|
21
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : (many ?? `${one}s`)}`;
|
|
22
|
+
const fixed = (v, known, fallback) => (typeof v === 'string' && known.has(v) ? v : fallback);
|
|
23
|
+
const PATH_RE = /(^|[\s("'`=:,;[{<])(file:\/\/\S+|~\/\S+|\/(?:Users|home|private|var|tmp|opt|etc|Volumes|root|srv|mnt|media|Applications|Library|System|usr|bin|sbin|lib|dev|proc|sys|run)\/\S+|[A-Za-z]:\\\S+)/g;
|
|
24
|
+
/** Free text for the phone: cleaned, scrubbed, capped, with absolute and home file paths replaced by words (best effort; repo-relative names stay). */
|
|
25
|
+
export function freeText(v, max) {
|
|
26
|
+
const s = cleanText(v, max * 2);
|
|
27
|
+
if (!s)
|
|
28
|
+
return null;
|
|
29
|
+
return cleanText(s.replace(PATH_RE, '$1[a file]'), max);
|
|
30
|
+
}
|
|
31
|
+
function machineName(id, nicknameOf) {
|
|
32
|
+
return cleanText(verifiedEntityName(id, nicknameOf(id)), 60);
|
|
33
|
+
}
|
|
34
|
+
function namesOf(ids, nicknameOf) {
|
|
35
|
+
return { count: ids.length, names: ids.map((id) => machineName(id, nicknameOf)).filter((n) => !!n) };
|
|
36
|
+
}
|
|
37
|
+
function proposalRow(p, queued) {
|
|
38
|
+
return {
|
|
39
|
+
key: rowKey('proposal', String(p.proposalId ?? '')),
|
|
40
|
+
summary: freeText(p.llmSummary, 400) ?? 'No summary was written for this proposal.',
|
|
41
|
+
hypothesis: freeText(p.hypothesis, 400),
|
|
42
|
+
code: freeText(p.suggestedErrorCode, 80),
|
|
43
|
+
occurrences: count(p.occurrencesObserved), restarts: count(p.processLifetimes),
|
|
44
|
+
generatedAt: iso(ts(p.generatedAt)), queued, machine: null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** This machine's summary (also the `?scope=local` answer a peer reads). */
|
|
48
|
+
export function summarizeLocalProposals(self) {
|
|
49
|
+
if (self.state !== 'ok' || !Array.isArray(self.data))
|
|
50
|
+
return { selfState: 'unavailable', outstanding: null, rows: [], truncated: false, dismissed: null, promoted: null };
|
|
51
|
+
const all = arr(self.data).filter((p) => typeof p.proposalId === 'string');
|
|
52
|
+
if (self.data.length > 0 && all.length === 0)
|
|
53
|
+
return { selfState: 'unavailable', outstanding: null, rows: [], truncated: false, dismissed: null, promoted: null };
|
|
54
|
+
// Newest first, once each: the same id can sit in two proposal directories on one machine.
|
|
55
|
+
const ids = new Set();
|
|
56
|
+
const outstanding = all.filter((p) => p.status === 'outstanding').sort((a, b) => ts(b.generatedAt) - ts(a.generatedAt)).filter((p) => !ids.has(p.proposalId) && !!ids.add(p.proposalId));
|
|
57
|
+
const rows = outstanding.map((p, i) => proposalRow(p, i >= VISIBLE_PROPOSALS));
|
|
58
|
+
return {
|
|
59
|
+
selfState: 'ok', outstanding: rows.length, allKeys: rows.map((r) => r.key).slice(0, MAX_PROPOSAL_KEYS), rows: rows.slice(0, MAX_PROPOSAL_ROWS), truncated: rows.length > MAX_PROPOSAL_ROWS,
|
|
60
|
+
dismissed: all.filter((p) => p.status === 'dismissed').length,
|
|
61
|
+
promoted: all.filter((p) => p.status === 'promoted').length,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** A peer's answer counts only with its own counts in place. */
|
|
65
|
+
export function isProposalsAnswer(d) {
|
|
66
|
+
if (d.schemaVersion !== MOBILE_PROPOSALS_INSIDE_SCHEMA_VERSION || d.selfState !== 'ok' || !Array.isArray(d.rows) || !Array.isArray(d.allKeys))
|
|
67
|
+
return false;
|
|
68
|
+
if (![d.outstanding, d.dismissed, d.promoted].every((n) => Number.isInteger(n) && n >= 0))
|
|
69
|
+
return false;
|
|
70
|
+
// The key list must account for the count, up to its cap: a count with no keys proves nothing.
|
|
71
|
+
return d.allKeys.every((k) => typeof k === 'string') && new Set(d.allKeys).size === Math.min(d.outstanding, MAX_PROPOSAL_KEYS);
|
|
72
|
+
}
|
|
73
|
+
function peerProposal(p, machineId, machine, i) {
|
|
74
|
+
const summary = freeText(p.summary, 400);
|
|
75
|
+
if (!summary)
|
|
76
|
+
return null;
|
|
77
|
+
return {
|
|
78
|
+
key: rowKey('proposal-peer', `${machineId}:${typeof p.key === 'string' ? p.key : i}`), summary,
|
|
79
|
+
hypothesis: freeText(p.hypothesis, 400), code: freeText(p.code, 80),
|
|
80
|
+
occurrences: count(p.occurrences), restarts: count(p.restarts), generatedAt: iso(ts(p.generatedAt)),
|
|
81
|
+
queued: p.queued === true, machine,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function summarizeProposals(self, peers, opts) {
|
|
85
|
+
const local = summarizeLocalProposals(self);
|
|
86
|
+
const rows = [...local.rows];
|
|
87
|
+
// The same proposal file can sit on two machines (a proposal's key is stable across them): count it once.
|
|
88
|
+
const seen = new Set(rows.map((r) => r.key));
|
|
89
|
+
const keys = new Set(Array.isArray(local.allKeys) ? local.allKeys : []);
|
|
90
|
+
let dismissed = count(local.dismissed);
|
|
91
|
+
let promoted = count(local.promoted);
|
|
92
|
+
let extra = Math.max(0, count(local.outstanding) - keys.size);
|
|
93
|
+
let truncated = local.truncated === true;
|
|
94
|
+
let peerOk = false;
|
|
95
|
+
const unreadIds = [];
|
|
96
|
+
for (const p of peers) {
|
|
97
|
+
const d = p.state === 'ok' ? rec(p.data) : null;
|
|
98
|
+
if (!d || !isProposalsAnswer(d)) {
|
|
99
|
+
unreadIds.push(p.machineId);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
peerOk = true;
|
|
103
|
+
const name = machineName(p.machineId, opts.nicknameOf) ?? 'another machine';
|
|
104
|
+
const theirKeys = d.allKeys.filter((x) => typeof x === 'string').slice(0, MAX_PROPOSAL_KEYS);
|
|
105
|
+
extra += Math.max(0, count(d.outstanding) - theirKeys.length);
|
|
106
|
+
for (const key of theirKeys)
|
|
107
|
+
keys.add(key);
|
|
108
|
+
arr(d.rows).forEach((x, i) => {
|
|
109
|
+
if (typeof x.key === 'string' && seen.has(x.key))
|
|
110
|
+
return;
|
|
111
|
+
if (typeof x.key === 'string')
|
|
112
|
+
seen.add(x.key);
|
|
113
|
+
const one = peerProposal(x, p.machineId, name, i);
|
|
114
|
+
if (one)
|
|
115
|
+
rows.push(one);
|
|
116
|
+
});
|
|
117
|
+
dismissed += count(d.dismissed);
|
|
118
|
+
promoted += count(d.promoted);
|
|
119
|
+
if (d.truncated === true)
|
|
120
|
+
truncated = true;
|
|
121
|
+
}
|
|
122
|
+
rows.sort((a, b) => ts(b.generatedAt) - ts(a.generatedAt));
|
|
123
|
+
const total = keys.size;
|
|
124
|
+
// Past MAX_PROPOSAL_KEYS on a machine the union is a floor, and the answer says so.
|
|
125
|
+
const atLeast = extra > 0;
|
|
126
|
+
const unread = namesOf(unreadIds, opts.nicknameOf);
|
|
127
|
+
const offline = namesOf(opts.offlineMachineIds ?? [], opts.nicknameOf);
|
|
128
|
+
const selfOk = local.selfState === 'ok';
|
|
129
|
+
const peersUnknown = opts.peersRead === false;
|
|
130
|
+
const state = selfOk || peerOk ? 'ok' : 'unavailable';
|
|
131
|
+
const partial = !selfOk || unread.count > 0 || offline.count > 0 || peersUnknown;
|
|
132
|
+
const scope = partial ? ' on the machines that answered' : '';
|
|
133
|
+
const headline = state === 'unavailable' ? "Can't read proposals right now."
|
|
134
|
+
: total ? `${atLeast ? 'At least ' : ''}${plural(total, 'proposed fix', 'proposed fixes')} ${total === 1 ? 'is' : 'are'} waiting${scope}.`
|
|
135
|
+
: `No proposed fixes are waiting${scope}.`;
|
|
136
|
+
return {
|
|
137
|
+
state, selfState: local.selfState, headline, outstanding: state === 'ok' ? total : null, atLeast, rows: rows.slice(0, MAX_PROPOSAL_ROWS),
|
|
138
|
+
truncated: truncated || rows.length > MAX_PROPOSAL_ROWS,
|
|
139
|
+
dismissed: state === 'ok' ? dismissed : null, promoted: state === 'ok' ? promoted : null, unread, offline, peersUnknown,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// ── inside ──────────────────────────────────────────────────────────────────
|
|
143
|
+
export const KIND_WORDS = {
|
|
144
|
+
commitment: 'Commitment', agreement: 'Agreement', 'thread-opened': 'Thread opened', 'thread-closed': 'Thread closed',
|
|
145
|
+
'thread-abandoned': 'Thread marked abandoned', decision: 'Decision', note: 'Note',
|
|
146
|
+
};
|
|
147
|
+
const WITH_WORDS = { user: 'with a person', agent: 'with another agent', self: 'with myself', system: 'with the system' };
|
|
148
|
+
const STATUS_WORDS = { open: 'Open', resolved: 'Resolved', cancelled: 'Cancelled', expired: 'Expired', stranded: 'Stranded', disputed: 'Disputed' };
|
|
149
|
+
const STATUS_UNKNOWN = "Status unknown: later history isn't loaded";
|
|
150
|
+
const STATUS_NOT_RECORDED = 'Status not recorded';
|
|
151
|
+
const LIFECYCLE_PREFIX = /^(expired|stranded|cancelled|resolved):/;
|
|
152
|
+
export const INSIGHT_STATUS_WORDS = {
|
|
153
|
+
discovered: 'Just noticed', 'acted-on': 'Being looked into', 'verified-effective': 'Fixed, and it stayed fixed',
|
|
154
|
+
'verified-ineffective': "The fix didn't hold", inconclusive: 'Watched, nothing conclusive', dismissed: 'Set aside',
|
|
155
|
+
};
|
|
156
|
+
export const VERDICT = {
|
|
157
|
+
supported: 'supports', corroborated: 'supports', refuted: 'contradicts', 'uncorroborated-contradicted': 'contradicts',
|
|
158
|
+
unverifiable: 'unverified', 'uncorroborated-unknown': 'unverified', 'not-eligible': 'not-evaluated',
|
|
159
|
+
};
|
|
160
|
+
export const VERDICT_WORDS = { supports: 'Evidence supports it', contradicts: 'Evidence contradicts it', unverified: "Couldn't verify", 'not-evaluated': 'Not evaluated' };
|
|
161
|
+
export const PREDICATE_WORDS = {
|
|
162
|
+
'session.elapsed-ms': 'how long a session had run', 'session.state': 'what state a session was in', 'commitment.state': 'where a promise stood',
|
|
163
|
+
'guard.state': 'whether a safety check was on', 'pull-request.merged': 'that a pull request was merged', 'pull-request.checks-pass': "that a pull request's checks passed",
|
|
164
|
+
'tool-action.completed': 'that something was done', 'capacity.limit': 'a usage limit', 'operator.attributed': 'who asked for something', 'external.fact': 'a fact about the outside world',
|
|
165
|
+
};
|
|
166
|
+
export const ACTION_WORDS = {
|
|
167
|
+
sent: 'a message was sent', deployed: 'something was deployed', 'handed-off': 'work was handed off', committed: 'a change was committed', pushed: 'a change was pushed', merged: 'a change was merged',
|
|
168
|
+
};
|
|
169
|
+
const CLAIM_DEFAULT = 'A claim in one of my messages';
|
|
170
|
+
export const REVIEW_WORDS = {
|
|
171
|
+
covered: 'Covered by an existing standard', 'needs-upgrade': 'A standard needs an upgrade', 'new-standard-needed': 'A new standard is needed',
|
|
172
|
+
'not-applicable': 'No standard change needed', 'process-gap': 'A gap in how I work', pending: 'Review pending', filled: 'Reviewed', 'dead-lettered': 'Review failed; needs a retry',
|
|
173
|
+
};
|
|
174
|
+
export const REVIEW_LIFECYCLE_WORDS = { open: 'Open', parked: 'Parked for later', resolved: 'Resolved', superseded: 'Combined into another review', reopened: 'Reopened' };
|
|
175
|
+
const LIFECYCLE_UNKNOWN = 'Status not recorded';
|
|
176
|
+
const KNOWN_KIND = new Set(Object.values(KIND_WORDS));
|
|
177
|
+
const KNOWN_WITH = new Set(Object.values(WITH_WORDS));
|
|
178
|
+
const KNOWN_STATUS = new Set([...Object.values(STATUS_WORDS), STATUS_UNKNOWN, STATUS_NOT_RECORDED]);
|
|
179
|
+
const KNOWN_REVIEW = new Set(Object.values(REVIEW_WORDS));
|
|
180
|
+
const KNOWN_INSIGHT = new Set(Object.values(INSIGHT_STATUS_WORDS));
|
|
181
|
+
const KNOWN_VERDICT = new Set(Object.values(VERDICT_WORDS));
|
|
182
|
+
const KNOWN_CLAIM = new Set([CLAIM_DEFAULT, ...Object.values(PREDICATE_WORDS).map((w) => `Said ${w}`), ...Object.values(ACTION_WORDS).map((w) => `Said ${w}`)]);
|
|
183
|
+
const KNOWN_REVIEW_LIFECYCLE = new Set([...Object.values(REVIEW_LIFECYCLE_WORDS), LIFECYCLE_UNKNOWN]);
|
|
184
|
+
const SECTION_STATES = new Set(['ok', 'off', 'unavailable']);
|
|
185
|
+
const TONES = new Set(['bad', 'attn', 'ok', 'unknown']);
|
|
186
|
+
/** A commitment's status: a stored non-open status wins; else a later record in the window; else unknown, never "open". */
|
|
187
|
+
export function commitmentWords(e, entries) {
|
|
188
|
+
const c = rec(e.commitment);
|
|
189
|
+
if (typeof c.status === 'string' && c.status !== 'open') {
|
|
190
|
+
const words = STATUS_WORDS[c.status];
|
|
191
|
+
return words ? { words, known: true, settled: true } : { words: STATUS_NOT_RECORDED, known: false, settled: false };
|
|
192
|
+
}
|
|
193
|
+
if (c.resolution)
|
|
194
|
+
return { words: STATUS_WORDS.resolved, known: true, settled: true };
|
|
195
|
+
let sup = null;
|
|
196
|
+
for (const x of entries)
|
|
197
|
+
if (x.supersedes === e.id && (!sup || ts(x.t) > ts(sup.t)))
|
|
198
|
+
sup = x;
|
|
199
|
+
if (sup) {
|
|
200
|
+
const m = typeof sup.subject === 'string' ? LIFECYCLE_PREFIX.exec(sup.subject) : null;
|
|
201
|
+
return { words: STATUS_WORDS[m ? m[1] : 'resolved'], known: true, settled: true };
|
|
202
|
+
}
|
|
203
|
+
return { words: STATUS_UNKNOWN, known: false, settled: false };
|
|
204
|
+
}
|
|
205
|
+
function activityRow(e, entries, now) {
|
|
206
|
+
let statusWords = null;
|
|
207
|
+
let overdue = false;
|
|
208
|
+
let tone = null;
|
|
209
|
+
if (e.kind === 'commitment') {
|
|
210
|
+
const s = commitmentWords(e, entries);
|
|
211
|
+
const deadline = ts(rec(e.commitment).deadline);
|
|
212
|
+
statusWords = s.words;
|
|
213
|
+
overdue = deadline > 0 && deadline < now && !s.settled;
|
|
214
|
+
tone = overdue || s.words === STATUS_WORDS.expired || s.words === STATUS_WORDS.stranded ? 'attn' : s.known ? null : 'unknown';
|
|
215
|
+
}
|
|
216
|
+
else if (e.kind === 'thread-abandoned' || (e.kind === 'note' && e.disputes))
|
|
217
|
+
tone = 'attn';
|
|
218
|
+
const type = rec(e.counterparty).type;
|
|
219
|
+
return {
|
|
220
|
+
key: rowKey('inside-record', `self:${String(e.id)}`),
|
|
221
|
+
kindWords: KIND_WORDS[String(e.kind)] ?? 'Record',
|
|
222
|
+
subject: freeText(e.subject, 240) ?? 'No subject recorded',
|
|
223
|
+
withWords: typeof type === 'string' ? WITH_WORDS[type] ?? null : null,
|
|
224
|
+
statusWords, overdue, at: iso(ts(e.t)), tone,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function summarizeActivity(self, now) {
|
|
228
|
+
if (self.state === 'off')
|
|
229
|
+
return { state: 'off', line: 'This record is off on this agent.', rows: [], truncated: false };
|
|
230
|
+
const raw = self.state === 'ok' ? rec(self.data).entries : null;
|
|
231
|
+
if (!Array.isArray(raw))
|
|
232
|
+
return { state: 'unavailable', line: "Can't tell what my other conversations have been doing.", rows: [], truncated: false };
|
|
233
|
+
const entries = arr(raw).filter((e) => typeof e.id === 'string').sort((a, b) => ts(b.t) - ts(a.t));
|
|
234
|
+
if (raw.length > 0 && entries.length === 0)
|
|
235
|
+
return { state: 'unavailable', line: "Can't tell what my other conversations have been doing.", rows: [], truncated: false };
|
|
236
|
+
const n = entries.length;
|
|
237
|
+
const kindCount = (k) => entries.filter((e) => e.kind === k).length;
|
|
238
|
+
const parts = [];
|
|
239
|
+
if (kindCount('decision'))
|
|
240
|
+
parts.push(plural(kindCount('decision'), 'decision'));
|
|
241
|
+
if (kindCount('agreement'))
|
|
242
|
+
parts.push(plural(kindCount('agreement'), 'agreement'));
|
|
243
|
+
if (kindCount('thread-opened'))
|
|
244
|
+
parts.push(`${plural(kindCount('thread-opened'), 'thread')} opened`);
|
|
245
|
+
if (kindCount('thread-closed'))
|
|
246
|
+
parts.push(`${kindCount('thread-closed')} closed`);
|
|
247
|
+
if (kindCount('thread-abandoned'))
|
|
248
|
+
parts.push(`${kindCount('thread-abandoned')} marked abandoned`);
|
|
249
|
+
const line = n === 0 ? 'Nothing recorded recently.' : `Among the ${n} most recent records: ${parts.length ? parts.join(', ') : 'only notes and commitments'}.`;
|
|
250
|
+
return { state: 'ok', line, rows: entries.slice(0, MAX_ACTIVITY_ROWS).map((e) => activityRow(e, entries, now)), truncated: n > MAX_ACTIVITY_ROWS };
|
|
251
|
+
}
|
|
252
|
+
/** A non-empty list none of whose elements has `field` is a bad read, not an empty one. */
|
|
253
|
+
const allMalformed = (list, ...fields) => list.length > 0 && !arr(list).some((x) => fields.some((f) => typeof x[f] === 'string' || typeof x[f] === 'boolean'));
|
|
254
|
+
/** The line for a count of repeating problems; `top` is the first one's cleaned summary. */
|
|
255
|
+
export function repeatLine(n, top) {
|
|
256
|
+
const words = top ?? 'a repeating problem';
|
|
257
|
+
return n === 1 ? `One kind of problem keeps coming back: ${words.replace(/\.$/, '')}.` : `${n} kinds of problem keep coming back; the first: ${words.replace(/\.$/, '')}.`;
|
|
258
|
+
}
|
|
259
|
+
export function claimLine(shown, contradicted, checked) {
|
|
260
|
+
return shown === 0 ? 'No claims checked yet.'
|
|
261
|
+
: contradicted ? `Of the last ${shown} observations, ${contradicted} ${contradicted === 1 ? 'contradicts' : 'contradict'} what I said.`
|
|
262
|
+
: checked ? 'Recent claim observations: none contradict what I said.'
|
|
263
|
+
: "Recent claims couldn't be verified either way.";
|
|
264
|
+
}
|
|
265
|
+
const insightActive = (i) => i.status !== 'verified-effective' && i.status !== 'dismissed';
|
|
266
|
+
const insightCameBack = (i) => i.status === 'verified-ineffective' || count(i.reopenCount) > 0;
|
|
267
|
+
export function summarizeProblems(insights, week) {
|
|
268
|
+
if (insights.state === 'off' || week.state === 'off')
|
|
269
|
+
return { state: 'off', line: 'Failure watching is off on this agent.', weekTotal: null, repeating: 0, cameBack: 0, rows: [] };
|
|
270
|
+
const rawList = insights.state === 'ok' ? rec(insights.data).insights : null;
|
|
271
|
+
const list = Array.isArray(rawList) && !allMalformed(rawList, 'status') ? arr(rawList).filter((i) => typeof i.status === 'string') : null;
|
|
272
|
+
const total = week.state === 'ok' ? rec(week.data).total : undefined;
|
|
273
|
+
const weekTotal = Number.isInteger(total) && total >= 0 ? total : null;
|
|
274
|
+
if (!list) {
|
|
275
|
+
const line = `Can't tell whether anything is repeating.${weekTotal != null ? ` ${plural(weekTotal, 'problem')} recorded this week.` : ''}`;
|
|
276
|
+
return { state: 'unavailable', line, weekTotal, repeating: null, cameBack: null, rows: [] };
|
|
277
|
+
}
|
|
278
|
+
const active = list.filter(insightActive);
|
|
279
|
+
// Settled problems never count: the phone says "came back" only about one still active.
|
|
280
|
+
const cameBack = active.filter(insightCameBack);
|
|
281
|
+
const first = cameBack[0] ?? active[0];
|
|
282
|
+
const line = active.length ? repeatLine(active.length, first ? freeText(first.summary, 200) : null)
|
|
283
|
+
: weekTotal == null ? "Nothing repeating. Can't tell this week's total."
|
|
284
|
+
: weekTotal === 0 ? 'Nothing repeating, and nothing new this week.'
|
|
285
|
+
: `Nothing repeating. ${weekTotal === 1 ? '1 one-off problem' : `${weekTotal} one-off problems`} this week.`;
|
|
286
|
+
const ordered = [...active.filter(insightCameBack), ...active.filter((i) => !insightCameBack(i)), ...list.filter((i) => !insightActive(i))];
|
|
287
|
+
const rows = ordered.slice(0, MAX_PROBLEM_ROWS).map((i) => ({
|
|
288
|
+
key: rowKey('inside-problem', `self:${String(i.id ?? i.summary)}`),
|
|
289
|
+
summary: freeText(i.summary, 280) ?? 'A repeating problem',
|
|
290
|
+
statusWords: INSIGHT_STATUS_WORDS[String(i.status)] ?? 'Status not recorded',
|
|
291
|
+
changes: count(i.distinctSessions), since: iso(ts(i.discoveredAt)),
|
|
292
|
+
tone: !insightActive(i) ? 'unknown' : insightCameBack(i) ? 'attn' : null,
|
|
293
|
+
}));
|
|
294
|
+
return { state: 'ok', line, weekTotal, weekUnread: weekTotal == null, repeating: active.length, cameBack: cameBack.length, rows };
|
|
295
|
+
}
|
|
296
|
+
function claimBucket(r) {
|
|
297
|
+
if (typeof r.event === 'string')
|
|
298
|
+
return null;
|
|
299
|
+
if (r.evaluated === false && !r.verdict)
|
|
300
|
+
return null;
|
|
301
|
+
if (r.evaluated === false)
|
|
302
|
+
return 'not-evaluated';
|
|
303
|
+
// A verdict word this version doesn't know is not a pass: it reads as unverified.
|
|
304
|
+
return typeof r.verdict === 'string' ? VERDICT[r.verdict] ?? 'unverified' : null;
|
|
305
|
+
}
|
|
306
|
+
function claimSentence(r) {
|
|
307
|
+
const p = typeof r.predicate === 'string' ? PREDICATE_WORDS[r.predicate] : undefined;
|
|
308
|
+
if (p)
|
|
309
|
+
return `Said ${p}`;
|
|
310
|
+
const a = typeof r.actionKind === 'string' ? ACTION_WORDS[r.actionKind] : undefined;
|
|
311
|
+
return a ? `Said ${a}` : CLAIM_DEFAULT;
|
|
312
|
+
}
|
|
313
|
+
export function summarizeClaims(self) {
|
|
314
|
+
if (self.state === 'off')
|
|
315
|
+
return { state: 'off', line: 'Checking my own claims is off on this agent.', contradicted: 0, checked: 0, rows: [] };
|
|
316
|
+
if (self.state !== 'ok' || !Array.isArray(self.data) || allMalformed(self.data, 'verdict', 'event', 'evaluated'))
|
|
317
|
+
return { state: 'unavailable', line: "Can't tell how my recent claims held up.", contradicted: null, checked: null, rows: [] };
|
|
318
|
+
const when = (r) => ts(r.observedAt ?? r.ts);
|
|
319
|
+
const ordered = arr(self.data).map((r, i) => ({ r, i })).sort((a, b) => (when(b.r) - when(a.r)) || (b.i - a.i)).map((x) => x.r);
|
|
320
|
+
const rows = ordered.flatMap((r, i) => {
|
|
321
|
+
const b = claimBucket(r);
|
|
322
|
+
if (!b)
|
|
323
|
+
return [];
|
|
324
|
+
return [{ key: rowKey('inside-claim', `self:${String(r.eventUuid ?? r.claimId ?? i)}`), sentence: claimSentence(r), verdictWords: VERDICT_WORDS[b], at: iso(when(r)), tone: b === 'contradicts' ? 'bad' : b === 'supports' ? null : 'unknown' }];
|
|
325
|
+
});
|
|
326
|
+
const contradicted = rows.filter((r) => r.tone === 'bad').length;
|
|
327
|
+
const n = rows.length;
|
|
328
|
+
// `checked` counts only observations the evidence settled; "couldn't verify" is never a pass.
|
|
329
|
+
const checked = rows.filter((r) => r.tone !== 'unknown').length;
|
|
330
|
+
return { state: 'ok', line: claimLine(n, contradicted, checked), contradicted, checked, rows };
|
|
331
|
+
}
|
|
332
|
+
function reviewWords(r) {
|
|
333
|
+
const s = rec(r.standardReview).verdict;
|
|
334
|
+
const p = rec(r.processReview).verdict;
|
|
335
|
+
if (!s && !p)
|
|
336
|
+
return REVIEW_WORDS[String(r.fillState)] ?? 'Review pending';
|
|
337
|
+
const parts = [typeof s === 'string' ? REVIEW_WORDS[s] : null, typeof p === 'string' && p !== 'covered' && p !== 'not-applicable' ? REVIEW_WORDS[p] : null].filter(Boolean);
|
|
338
|
+
return parts.join('; ') || 'Reviewed';
|
|
339
|
+
}
|
|
340
|
+
export function summarizeReviews(self) {
|
|
341
|
+
if (self.state === 'off')
|
|
342
|
+
return { state: 'off', open: 0, rows: [] };
|
|
343
|
+
const d = self.state === 'ok' ? rec(self.data) : null;
|
|
344
|
+
if (!d || !Array.isArray(d.records) || nullableCount(d.open) === null || allMalformed(d.records, 'dedupeKey'))
|
|
345
|
+
return { state: 'unavailable', open: null, rows: [] };
|
|
346
|
+
const rows = arr(d.records).filter((r) => typeof r.dedupeKey === 'string').sort((a, b) => ts(b.updatedAt) - ts(a.updatedAt)).slice(0, MAX_REVIEW_ROWS).map((r) => ({
|
|
347
|
+
key: rowKey('inside-review', `self:${String(r.dedupeKey)}`), words: reviewWords(r),
|
|
348
|
+
lifecycleWords: REVIEW_LIFECYCLE_WORDS[String(r.reviewLifecycle)] ?? LIFECYCLE_UNKNOWN, seen: count(r.recurrenceCount),
|
|
349
|
+
why: freeText(r.rationale, 280), at: iso(ts(r.updatedAt)), tone: r.fillState === 'dead-lettered' ? 'attn' : null,
|
|
350
|
+
}));
|
|
351
|
+
return { state: 'ok', open: d.open, rows };
|
|
352
|
+
}
|
|
353
|
+
/** The console's page headline, worst first, with what could not be read stated. All clear needs something measured. */
|
|
354
|
+
export function insideHeadline(activity, problems, claims, reviews) {
|
|
355
|
+
const unread = [
|
|
356
|
+
activity.state === 'unavailable' ? 'conversation history' : null,
|
|
357
|
+
problems.state === 'unavailable' ? 'recurring problems' : null,
|
|
358
|
+
problems.weekUnread === true ? "this week's total" : null,
|
|
359
|
+
claims.state === 'unavailable' ? 'claim checks' : null,
|
|
360
|
+
reviews.state === 'unavailable' ? 'correction reviews' : null,
|
|
361
|
+
].filter((x) => !!x);
|
|
362
|
+
const sub = unread.length ? `Couldn't read: ${unread.join(', ')}.` : null;
|
|
363
|
+
if ([activity, problems, claims].every((s) => s.state !== 'ok')) {
|
|
364
|
+
if (unread.length)
|
|
365
|
+
return { headline: "Can't tell how I'm doing inside.", tone: 'unknown', sub };
|
|
366
|
+
if (reviews.state !== 'ok')
|
|
367
|
+
return { headline: 'None of these checks are on for this machine.', tone: 'unknown', sub };
|
|
368
|
+
return { headline: 'Only correction reviews are on for this machine.', tone: 'unknown', sub };
|
|
369
|
+
}
|
|
370
|
+
const contradicted = count(claims.contradicted);
|
|
371
|
+
if (claims.state === 'ok' && contradicted) {
|
|
372
|
+
return { headline: `${contradicted === 1 ? 'One of my recent claims was' : `${contradicted} of my recent claims were`} contradicted by the evidence.`, tone: 'bad', sub };
|
|
373
|
+
}
|
|
374
|
+
if (problems.state === 'ok' && count(problems.cameBack))
|
|
375
|
+
return { headline: 'A problem I thought was fixed came back.', tone: 'attn', sub };
|
|
376
|
+
if (problems.state === 'ok' && count(problems.repeating))
|
|
377
|
+
return { headline: String(problems.line), tone: 'attn', sub };
|
|
378
|
+
const said = [problems.state === 'ok' ? 'nothing repeating' : null, claims.state === 'ok' && count(claims.checked) ? 'no recent claim contradicted' : null].filter(Boolean);
|
|
379
|
+
const text = said.length ? `${said.join(', ').replace(/^./, (c) => c.toUpperCase())}.` : String(activity.line);
|
|
380
|
+
return { headline: text, tone: said.length && !unread.length ? 'ok' : 'unknown', sub };
|
|
381
|
+
}
|
|
382
|
+
/** This machine's summary (also the `?scope=local` answer a peer reads). */
|
|
383
|
+
export function summarizeLocalInside(src, now) {
|
|
384
|
+
const activity = summarizeActivity(src.activity, now);
|
|
385
|
+
const problems = summarizeProblems(src.insights, src.week);
|
|
386
|
+
const claims = summarizeClaims(src.claims);
|
|
387
|
+
const reviews = summarizeReviews(src.reviews);
|
|
388
|
+
return { ...insideHeadline(activity, problems, claims, reviews), activity, problems, claims, reviews };
|
|
389
|
+
}
|
|
390
|
+
const SECTION_NAMES = ['activity', 'problems', 'claims', 'reviews'];
|
|
391
|
+
const nullableCount = (v) => (Number.isInteger(v) && v >= 0 ? v : null);
|
|
392
|
+
const sectionState = (v) => fixed(v, SECTION_STATES, 'unavailable');
|
|
393
|
+
const tone = (v) => (typeof v === 'string' && TONES.has(v) ? v : null);
|
|
394
|
+
const cleanReviewWords = (v) => {
|
|
395
|
+
if (typeof v !== 'string')
|
|
396
|
+
return LIFECYCLE_UNKNOWN;
|
|
397
|
+
if (KNOWN_REVIEW.has(v))
|
|
398
|
+
return v;
|
|
399
|
+
return v.split('; ').filter((w) => KNOWN_REVIEW.has(w)).join('; ') || LIFECYCLE_UNKNOWN;
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* A peer's already-summarized sections, rebuilt field by field (peer text is untrusted), named by its
|
|
403
|
+
* machine. A section that says `ok` without its counts reads `unavailable`. Every line and the headline
|
|
404
|
+
* are rebuilt here from validated counts and rows: the peer's own sentences are not shown.
|
|
405
|
+
*/
|
|
406
|
+
export function peerInside(d, machineId, machine) {
|
|
407
|
+
const k = (kind, r, i) => rowKey(`inside-peer-${kind}`, `${machineId}:${typeof r.key === 'string' ? r.key : i}`);
|
|
408
|
+
const a = rec(d.activity);
|
|
409
|
+
const p = rec(d.problems);
|
|
410
|
+
const c = rec(d.claims);
|
|
411
|
+
const v = rec(d.reviews);
|
|
412
|
+
const activityRows = arr(a.rows).slice(0, MAX_ACTIVITY_ROWS).map((r, i) => ({
|
|
413
|
+
key: k('record', r, i), kindWords: fixed(r.kindWords, KNOWN_KIND, 'Record'), subject: freeText(r.subject, 240) ?? 'No subject recorded',
|
|
414
|
+
withWords: typeof r.withWords === 'string' && KNOWN_WITH.has(r.withWords) ? r.withWords : null,
|
|
415
|
+
statusWords: typeof r.statusWords === 'string' && KNOWN_STATUS.has(r.statusWords) ? r.statusWords : null,
|
|
416
|
+
overdue: r.overdue === true, at: iso(ts(r.at)), tone: tone(r.tone),
|
|
417
|
+
}));
|
|
418
|
+
const problemRows = arr(p.rows).slice(0, MAX_PROBLEM_ROWS).map((r, i) => ({
|
|
419
|
+
key: k('problem', r, i), summary: freeText(r.summary, 280) ?? 'A repeating problem', statusWords: fixed(r.statusWords, KNOWN_INSIGHT, 'Status not recorded'),
|
|
420
|
+
changes: count(r.changes), since: iso(ts(r.since)), tone: tone(r.tone),
|
|
421
|
+
}));
|
|
422
|
+
// Tone and the counts come from the validated verdict words (all rows are sent), never from the peer's numbers.
|
|
423
|
+
const claimRows = arr(c.rows).slice(0, CLAIMS_WINDOW).map((r, i) => {
|
|
424
|
+
const verdictWords = fixed(r.verdictWords, KNOWN_VERDICT, VERDICT_WORDS.unverified);
|
|
425
|
+
return {
|
|
426
|
+
key: k('claim', r, i), sentence: fixed(r.sentence, KNOWN_CLAIM, CLAIM_DEFAULT), verdictWords, at: iso(ts(r.at)),
|
|
427
|
+
tone: verdictWords === VERDICT_WORDS.contradicts ? 'bad' : verdictWords === VERDICT_WORDS.supports ? null : 'unknown',
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
const repeating = nullableCount(p.repeating);
|
|
431
|
+
const cameBack = nullableCount(p.cameBack);
|
|
432
|
+
const claimCounts = nullableCount(c.contradicted) !== null && nullableCount(c.checked) !== null;
|
|
433
|
+
const contradicted = claimCounts ? claimRows.filter((r) => r.tone === 'bad').length : null;
|
|
434
|
+
const checked = claimCounts ? claimRows.filter((r) => r.tone !== 'unknown').length : null;
|
|
435
|
+
const weekTotal = nullableCount(p.weekTotal);
|
|
436
|
+
const aState = sectionState(a.state);
|
|
437
|
+
const pState = sectionState(p.state) === 'ok' && (repeating === null || cameBack === null) ? 'unavailable' : sectionState(p.state);
|
|
438
|
+
// Numbers that disagree with the rows mean the two machines don't read each other's words: can't tell.
|
|
439
|
+
const claimsAgree = contradicted === nullableCount(c.contradicted) && checked === nullableCount(c.checked);
|
|
440
|
+
const cState = sectionState(c.state) === 'ok' && (contradicted === null || checked === null || !claimsAgree) ? 'unavailable' : sectionState(c.state);
|
|
441
|
+
const sections = {
|
|
442
|
+
activity: {
|
|
443
|
+
state: aState, truncated: a.truncated === true, rows: activityRows,
|
|
444
|
+
line: aState === 'off' ? 'This record is off on this agent.' : aState !== 'ok' ? "Can't tell what this machine's conversations have been doing."
|
|
445
|
+
: activityRows.length ? `${plural(activityRows.length, 'recent record')} shown.` : 'Nothing recorded recently.',
|
|
446
|
+
},
|
|
447
|
+
problems: {
|
|
448
|
+
state: pState, weekTotal, weekUnread: pState === 'ok' && weekTotal === null, repeating, cameBack, rows: problemRows,
|
|
449
|
+
line: pState === 'off' ? 'Failure watching is off on this agent.' : pState !== 'ok' ? "Can't tell whether anything is repeating."
|
|
450
|
+
: repeating ? repeatLine(repeating, problemRows[0]?.summary ?? null) : 'Nothing repeating.',
|
|
451
|
+
},
|
|
452
|
+
claims: {
|
|
453
|
+
state: cState, contradicted, checked, rows: claimRows,
|
|
454
|
+
line: cState === 'off' ? 'Checking my own claims is off on this agent.' : cState !== 'ok' ? "Can't tell how recent claims held up."
|
|
455
|
+
: claimLine(claimRows.length, contradicted ?? 0, checked ?? 0),
|
|
456
|
+
},
|
|
457
|
+
reviews: {
|
|
458
|
+
state: sectionState(v.state) === 'ok' && nullableCount(v.open) === null ? 'unavailable' : sectionState(v.state), open: nullableCount(v.open),
|
|
459
|
+
rows: arr(v.rows).slice(0, MAX_REVIEW_ROWS).map((r, i) => ({
|
|
460
|
+
key: k('review', r, i), words: cleanReviewWords(r.words), lifecycleWords: fixed(r.lifecycleWords, KNOWN_REVIEW_LIFECYCLE, LIFECYCLE_UNKNOWN),
|
|
461
|
+
seen: count(r.seen), why: freeText(r.why, 280), at: iso(ts(r.at)), tone: tone(r.tone),
|
|
462
|
+
})),
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
return { machine, ...insideHeadline(sections.activity, sections.problems, sections.claims, sections.reviews), ...sections };
|
|
466
|
+
}
|
|
467
|
+
/** Every `ok` section also has its counts (an answer that fails this still shows, those sections unavailable, and is reported). */
|
|
468
|
+
export function isCompleteInsideAnswer(d) {
|
|
469
|
+
if (!isInsideAnswer(d))
|
|
470
|
+
return false;
|
|
471
|
+
const need = (name, fields) => rec(d[name]).state !== 'ok' || fields.every((f) => nullableCount(rec(d[name])[f]) !== null);
|
|
472
|
+
return need('problems', ['repeating', 'cameBack']) && need('claims', ['contradicted', 'checked']) && need('reviews', ['open']);
|
|
473
|
+
}
|
|
474
|
+
const hasSection = (d, name) => {
|
|
475
|
+
const s = rec(d[name]);
|
|
476
|
+
return SECTION_STATES.has(String(s.state)) && Array.isArray(s.rows);
|
|
477
|
+
};
|
|
478
|
+
/** A peer answer counts only when all four sections are there in the expected shape. */
|
|
479
|
+
export function isInsideAnswer(d) {
|
|
480
|
+
return d.schemaVersion === MOBILE_PROPOSALS_INSIDE_SCHEMA_VERSION && SECTION_NAMES.every((n) => hasSection(d, n));
|
|
481
|
+
}
|
|
482
|
+
const TONE_RANK = { bad: 3, attn: 2, unknown: 1, ok: 0 };
|
|
483
|
+
export function summarizeInside(self, peers, opts) {
|
|
484
|
+
const machines = [];
|
|
485
|
+
const unreadIds = [];
|
|
486
|
+
for (const p of peers) {
|
|
487
|
+
const d = p.state === 'ok' ? rec(p.data) : null;
|
|
488
|
+
if (!d || !isInsideAnswer(d)) {
|
|
489
|
+
unreadIds.push(p.machineId);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
machines.push(peerInside(d, p.machineId, machineName(p.machineId, opts.nicknameOf) ?? 'another machine'));
|
|
493
|
+
}
|
|
494
|
+
const unread = namesOf(unreadIds, opts.nicknameOf);
|
|
495
|
+
const offline = namesOf(opts.offlineMachineIds ?? [], opts.nicknameOf);
|
|
496
|
+
const peersUnknown = opts.peersRead === false;
|
|
497
|
+
const peerGap = machines.some((m) => rec(m.problems).weekUnread === true || SECTION_NAMES.some((n) => rec(m[n]).state === 'unavailable'));
|
|
498
|
+
const partial = unread.count > 0 || offline.count > 0 || peersUnknown || peerGap;
|
|
499
|
+
// The worst machine leads; a peer's headline says which machine it is about.
|
|
500
|
+
const rank = (m) => TONE_RANK[String(m.tone)] ?? 1;
|
|
501
|
+
let lead = self;
|
|
502
|
+
for (const m of machines)
|
|
503
|
+
if (rank(m) > rank(lead) && rank(m) >= 2)
|
|
504
|
+
lead = m;
|
|
505
|
+
const words = lead === self ? String(self.headline) : `${String(lead.machine)}: ${String(lead.headline)}`;
|
|
506
|
+
// A machine nobody could read keeps an all-clear from being all clear.
|
|
507
|
+
const worst = Math.max(rank(self), ...machines.map(rank), partial ? 1 : 0);
|
|
508
|
+
const tone = worst >= 2 ? String(lead.tone) : worst === 1 ? 'unknown' : 'ok';
|
|
509
|
+
return { headline: partial ? `${words.replace(/\.$/, '')} (on the machines that answered).` : words, tone, self, peers: machines, unread, offline, peersUnknown };
|
|
510
|
+
}
|
|
511
|
+
function reportReadFailure(route, err) {
|
|
512
|
+
try {
|
|
513
|
+
DegradationReporter.getInstance().report({
|
|
514
|
+
feature: 'MobileProposalsInside.read',
|
|
515
|
+
primary: `${route} answers the phone's native screen`,
|
|
516
|
+
fallback: 'the phone shows "can\'t tell" for that part and the owner can retry or open the console',
|
|
517
|
+
reason: err instanceof Error ? err.name : 'unknown error',
|
|
518
|
+
impact: 'The owner\'s phone cannot show this part until the read succeeds.',
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
catch { /* @silent-fallback-ok — reporting must never turn a 500 into a crash */ }
|
|
522
|
+
}
|
|
523
|
+
/** One source of an answer; a throw is reported and becomes `fallback` (never a 500 for the whole answer). */
|
|
524
|
+
async function safe(route, f, fallback) {
|
|
525
|
+
try {
|
|
526
|
+
return await f();
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
reportReadFailure(route, err);
|
|
530
|
+
return fallback;
|
|
531
|
+
} // @silent-fallback-ok — not silent: recorded through reportReadFailure (DegradationReporter); the answer says the part could not be read
|
|
532
|
+
}
|
|
533
|
+
export function mountMobileProposalsInsideRoutes(router, deps) {
|
|
534
|
+
const now = () => (deps.now ? deps.now() : Date.now());
|
|
535
|
+
const cached = singleFlight(POOL_READ_CACHE_MS, now);
|
|
536
|
+
const guard = (req, res) => {
|
|
537
|
+
res.set('Cache-Control', 'no-store');
|
|
538
|
+
if (ownerOrBearer(req, deps.bearerOk))
|
|
539
|
+
return true;
|
|
540
|
+
res.status(403).json({ error: 'owner-phone-required', detail: 'Only the owner\'s phone can see proposals and how the agent is doing inside.' });
|
|
541
|
+
return false;
|
|
542
|
+
};
|
|
543
|
+
const head = () => ({ schemaVersion: MOBILE_PROPOSALS_INSIDE_SCHEMA_VERSION, generatedAt: new Date(now()).toISOString() });
|
|
544
|
+
// `?scope=local`: this machine's summary only (how one machine reads another's; never fans out again).
|
|
545
|
+
const isLocal = (req) => req.query.scope === 'local';
|
|
546
|
+
const down = { state: 'unavailable' };
|
|
547
|
+
/** A read that answered but in the wrong shape: as much a failed read as a throw. */
|
|
548
|
+
const wrongShape = (route) => reportReadFailure(route, Object.assign(new Error('wrong shape'), { name: 'WrongShape' }));
|
|
549
|
+
const checkPeers = (route, peers, valid) => {
|
|
550
|
+
for (const p of peers)
|
|
551
|
+
if (p.state === 'ok' && !valid(rec(p.data)))
|
|
552
|
+
wrongShape(route);
|
|
553
|
+
};
|
|
554
|
+
/** Peers and offline machines, with whether that read itself worked (a failed read is not "no peers"). */
|
|
555
|
+
const peerRead = async (route, f) => {
|
|
556
|
+
const peers = await safe(route, f, null);
|
|
557
|
+
return { peers: peers ?? [], peersRead: peers !== null };
|
|
558
|
+
};
|
|
559
|
+
const offlineRead = () => {
|
|
560
|
+
try {
|
|
561
|
+
return { ids: deps.offlineMachineIds?.() ?? [], ok: true };
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
reportReadFailure('offline machine list', err);
|
|
565
|
+
return { ids: [], ok: false };
|
|
566
|
+
} // @silent-fallback-ok — reported; the answer then says it could not tell which machines exist
|
|
567
|
+
};
|
|
568
|
+
router.get('/mobile/proposals', async (req, res) => {
|
|
569
|
+
if (!guard(req, res))
|
|
570
|
+
return;
|
|
571
|
+
const local = isLocal(req);
|
|
572
|
+
try {
|
|
573
|
+
const body = await cached(local ? 'proposals:local' : 'proposals', async () => {
|
|
574
|
+
const [self, read] = await Promise.all([
|
|
575
|
+
safe('GET /mobile/proposals (this machine)', () => deps.proposals(), down),
|
|
576
|
+
local ? Promise.resolve({ peers: [], peersRead: true }) : peerRead('GET /mobile/proposals (peers)', deps.peerProposals),
|
|
577
|
+
]);
|
|
578
|
+
const mine = summarizeLocalProposals(self);
|
|
579
|
+
if (self.state === 'ok' && mine.selfState !== 'ok')
|
|
580
|
+
wrongShape('GET /mobile/proposals (this machine)');
|
|
581
|
+
if (local)
|
|
582
|
+
return { ...head(), ...mine };
|
|
583
|
+
checkPeers('GET /mobile/proposals (peers)', read.peers, isProposalsAnswer);
|
|
584
|
+
const off = offlineRead();
|
|
585
|
+
return { ...head(), ...summarizeProposals(self, read.peers, { nicknameOf: deps.nicknameOf, offlineMachineIds: off.ids, peersRead: read.peersRead && off.ok }) };
|
|
586
|
+
});
|
|
587
|
+
res.json(body);
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
reportReadFailure('GET /mobile/proposals', err);
|
|
591
|
+
res.status(500).json({ error: 'proposals-unavailable' });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
router.get('/mobile/inside', async (req, res) => {
|
|
595
|
+
if (!guard(req, res))
|
|
596
|
+
return;
|
|
597
|
+
const local = isLocal(req);
|
|
598
|
+
try {
|
|
599
|
+
const body = await cached(local ? 'inside:local' : 'inside', async () => {
|
|
600
|
+
const part = (name, f) => safe(`GET /mobile/inside (${name})`, f, down);
|
|
601
|
+
const [activity, insights, week, claims, reviews, read] = await Promise.all([
|
|
602
|
+
part('records', deps.activity), part('patterns', deps.insights), part('week', deps.week), part('claims', deps.claims), part('reviews', deps.reviews),
|
|
603
|
+
local ? Promise.resolve({ peers: [], peersRead: true }) : peerRead('GET /mobile/inside (peers)', deps.peerInside),
|
|
604
|
+
]);
|
|
605
|
+
const self = summarizeLocalInside({ activity, insights, week, claims, reviews }, now());
|
|
606
|
+
const sources = { activity, problems: insights, claims, reviews };
|
|
607
|
+
for (const n of SECTION_NAMES)
|
|
608
|
+
if (sources[n].state === 'ok' && rec(self[n]).state === 'unavailable')
|
|
609
|
+
wrongShape(`GET /mobile/inside (${n})`);
|
|
610
|
+
if (week.state === 'ok' && rec(self.problems).weekUnread === true)
|
|
611
|
+
wrongShape('GET /mobile/inside (week)');
|
|
612
|
+
if (local)
|
|
613
|
+
return { ...head(), ...self };
|
|
614
|
+
checkPeers('GET /mobile/inside (peers)', read.peers, isCompleteInsideAnswer);
|
|
615
|
+
const off = offlineRead();
|
|
616
|
+
return { ...head(), ...summarizeInside(self, read.peers, { nicknameOf: deps.nicknameOf, offlineMachineIds: off.ids, peersRead: read.peersRead && off.ok }) };
|
|
617
|
+
});
|
|
618
|
+
res.json(body);
|
|
619
|
+
}
|
|
620
|
+
catch (err) {
|
|
621
|
+
reportReadFailure('GET /mobile/inside', err);
|
|
622
|
+
res.status(500).json({ error: 'inside-unavailable' });
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
//# sourceMappingURL=mobileProposalsInsideRoutes.js.map
|