instar 1.3.676 → 1.3.677
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/server.d.ts.map +1 -1
- package/dist/commands/server.js +100 -9
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SpawningTopicsRegistry.d.ts +65 -0
- package/dist/core/SpawningTopicsRegistry.d.ts.map +1 -0
- package/dist/core/SpawningTopicsRegistry.js +75 -0
- package/dist/core/SpawningTopicsRegistry.js.map +1 -0
- package/dist/monitoring/TopicReachabilityVerifier.d.ts +92 -0
- package/dist/monitoring/TopicReachabilityVerifier.d.ts.map +1 -0
- package/dist/monitoring/TopicReachabilityVerifier.js +173 -0
- package/dist/monitoring/TopicReachabilityVerifier.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/{1.3.676.md → 1.3.677.md} +33 -0
- package/upgrades/side-effects/verify-after-reachability.md +65 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAwBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAwH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA+EtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA84CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAmfN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAyrhBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
|
package/dist/commands/server.js
CHANGED
|
@@ -22,6 +22,8 @@ import { loadConfig, ensureStateDir, detectTmuxPath, detectGeminiPath } from '..
|
|
|
22
22
|
import { handleProcessLevelError } from '../core/uncaughtExceptionPolicy.js';
|
|
23
23
|
import { planInboundLossNotices } from '../core/inboundLossRouting.js';
|
|
24
24
|
import { configureHostSpawnSemaphore } from '../core/hostSpawnSemaphore.js';
|
|
25
|
+
import { SpawningTopicsRegistry } from '../core/SpawningTopicsRegistry.js';
|
|
26
|
+
import { TopicReachabilityVerifier } from '../monitoring/TopicReachabilityVerifier.js';
|
|
25
27
|
import { SingleInstanceLock, installReleaseHandlers } from '../core/SingleInstanceLock.js';
|
|
26
28
|
import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGate.js';
|
|
27
29
|
import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
|
|
@@ -440,6 +442,10 @@ let _fixDeps = null;
|
|
|
440
442
|
// server's store at message-time — the server is constructed long after
|
|
441
443
|
// routing is wired, and the store must be the server's OWN instance.
|
|
442
444
|
let _agentServerRef = null;
|
|
445
|
+
// F7: late-bound ref to wireTelegramRouting's spawning-topics registry so the
|
|
446
|
+
// TopicReachabilityVerifier (constructed in the outer scope) can probe whether a topic
|
|
447
|
+
// is stuck-spawning. Assigned inside wireTelegramRouting; null until routing is wired.
|
|
448
|
+
let _spawningTopicsRegistryRef = null;
|
|
443
449
|
// Module-level reference for session resume mapping.
|
|
444
450
|
// Set once in startServer() and used by spawnSessionForTopic/respawnSessionForTopic.
|
|
445
451
|
let _topicResumeMap = null;
|
|
@@ -1731,7 +1737,12 @@ getAttentionTopicId) {
|
|
|
1731
1737
|
// Guard: tracks which topic IDs have a spawn in progress.
|
|
1732
1738
|
// Prevents duplicate concurrent spawns for the same topic when messages
|
|
1733
1739
|
// arrive faster than the async spawn completes.
|
|
1734
|
-
|
|
1740
|
+
// F7 Piece 1: token-tagged spawn-guard. `has` guards double-spawn (as the old Set
|
|
1741
|
+
// did); `add` returns a token and `clear` is token-guarded so a late `.finally` from
|
|
1742
|
+
// a superseded spawn cannot delete a newer entry (the ABA fix). A hung spawn keeps its
|
|
1743
|
+
// entry (no timeout/sweep) so the verifier can SURFACE it — never auto-cleared.
|
|
1744
|
+
const spawningTopics = new SpawningTopicsRegistry();
|
|
1745
|
+
_spawningTopicsRegistryRef = spawningTopics; // F7: expose to the outer-scope verifier
|
|
1735
1746
|
telegram.onTopicMessage = async (msg) => {
|
|
1736
1747
|
const topicId = msg.metadata?.messageThreadId ?? null;
|
|
1737
1748
|
if (!topicId)
|
|
@@ -2032,7 +2043,7 @@ getAttentionTopicId) {
|
|
|
2032
2043
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate respawn`);
|
|
2033
2044
|
return;
|
|
2034
2045
|
}
|
|
2035
|
-
spawningTopics.add(topicId);
|
|
2046
|
+
const _spawnTokA = spawningTopics.add(topicId);
|
|
2036
2047
|
// Remove the resume UUID so respawnSessionForTopic doesn't try --resume
|
|
2037
2048
|
if (_topicResumeMap) {
|
|
2038
2049
|
_topicResumeMap.remove(topicId);
|
|
@@ -2043,7 +2054,7 @@ getAttentionTopicId) {
|
|
|
2043
2054
|
telegram.sendToTopic(topicId, `❌ Fresh session restart failed. Try sending your message again.`).catch(() => { });
|
|
2044
2055
|
})
|
|
2045
2056
|
.finally(() => {
|
|
2046
|
-
spawningTopics.
|
|
2057
|
+
spawningTopics.clear(topicId, _spawnTokA);
|
|
2047
2058
|
});
|
|
2048
2059
|
}
|
|
2049
2060
|
else if (!isQuotaDeath) {
|
|
@@ -2054,7 +2065,7 @@ getAttentionTopicId) {
|
|
|
2054
2065
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate respawn`);
|
|
2055
2066
|
return;
|
|
2056
2067
|
}
|
|
2057
|
-
spawningTopics.add(topicId);
|
|
2068
|
+
const _spawnTokB = spawningTopics.add(topicId);
|
|
2058
2069
|
telegram.sendToTopic(topicId, `🔄 Session restarting — message queued.`).catch(() => { });
|
|
2059
2070
|
respawnSessionForTopic(sessionManager, telegram, targetSession, topicId, text, topicMemory, resolvedUser ?? undefined)
|
|
2060
2071
|
.catch(err => {
|
|
@@ -2066,7 +2077,7 @@ getAttentionTopicId) {
|
|
|
2066
2077
|
telegram.sendToTopic(topicId, userMsg).catch(() => { });
|
|
2067
2078
|
})
|
|
2068
2079
|
.finally(() => {
|
|
2069
|
-
spawningTopics.
|
|
2080
|
+
spawningTopics.clear(topicId, _spawnTokB);
|
|
2070
2081
|
});
|
|
2071
2082
|
}
|
|
2072
2083
|
}
|
|
@@ -2081,7 +2092,7 @@ getAttentionTopicId) {
|
|
|
2081
2092
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate`);
|
|
2082
2093
|
return;
|
|
2083
2094
|
}
|
|
2084
|
-
spawningTopics.add(topicId);
|
|
2095
|
+
const _spawnTokC = spawningTopics.add(topicId);
|
|
2085
2096
|
// Resolve topic name — try in-memory, then active probe, then fallback
|
|
2086
2097
|
let spawnName = storedTopicName;
|
|
2087
2098
|
if (!spawnName || /^topic-\d+$/.test(spawnName)) {
|
|
@@ -2104,7 +2115,7 @@ getAttentionTopicId) {
|
|
|
2104
2115
|
: 'Having trouble starting a session right now. Try sending your message again in a moment.';
|
|
2105
2116
|
telegram.sendToTopic(topicId, userMsg).catch(() => { });
|
|
2106
2117
|
}).finally(() => {
|
|
2107
|
-
spawningTopics.
|
|
2118
|
+
spawningTopics.clear(topicId, _spawnTokC);
|
|
2108
2119
|
});
|
|
2109
2120
|
}
|
|
2110
2121
|
};
|
|
@@ -2195,7 +2206,7 @@ getAttentionTopicId) {
|
|
|
2195
2206
|
return { kind: 'handover-refused' };
|
|
2196
2207
|
if (handover.stopRecheck())
|
|
2197
2208
|
return { kind: 'stopped-before-inject' };
|
|
2198
|
-
spawningTopics.add(topicId);
|
|
2209
|
+
const _spawnTokD = spawningTopics.add(topicId);
|
|
2199
2210
|
try {
|
|
2200
2211
|
if (targetSession) {
|
|
2201
2212
|
// Dead session — respawn, AWAITED through the spawn+inject (the PIS
|
|
@@ -2214,7 +2225,7 @@ getAttentionTopicId) {
|
|
|
2214
2225
|
return { kind: 'local-delivered', injectError: err instanceof Error ? err.message : String(err) };
|
|
2215
2226
|
}
|
|
2216
2227
|
finally {
|
|
2217
|
-
spawningTopics.
|
|
2228
|
+
spawningTopics.clear(topicId, _spawnTokD);
|
|
2218
2229
|
}
|
|
2219
2230
|
};
|
|
2220
2231
|
}
|
|
@@ -7632,7 +7643,87 @@ export async function startServer(options) {
|
|
|
7632
7643
|
}
|
|
7633
7644
|
}
|
|
7634
7645
|
}
|
|
7646
|
+
// ── F7 Verify-After Topic Reachability (PURE SIGNAL, dev-gated dark/fleet) ──
|
|
7647
|
+
// After a destructive mutation it verifies the topic is still inbound-reachable and
|
|
7648
|
+
// SURFACES a genuine orphan (one NORMAL attention item). It mutates NOTHING. The
|
|
7649
|
+
// probe is conservative + fail-safe: a live session ⇒ reachable; a topic stuck-
|
|
7650
|
+
// spawning past stuckSpawnMs (the registry seam) ⇒ orphan; anything else ⇒ reachable
|
|
7651
|
+
// (the next inbound auto-spawns — the self-heal), so it never false-orphans an idle kill.
|
|
7652
|
+
const _trvCfg = config.monitoring?.topicReachabilityVerifier;
|
|
7653
|
+
const _trvEnabled = resolveDevAgentGate(_trvCfg?.enabled, config);
|
|
7654
|
+
const _stuckSpawnMs = _trvCfg?.stuckSpawnMs ?? 180_000;
|
|
7655
|
+
const topicReachabilityVerifier = _trvEnabled
|
|
7656
|
+
? new TopicReachabilityVerifier({
|
|
7657
|
+
now: () => Date.now(),
|
|
7658
|
+
graceMs: _trvCfg?.graceMs ?? 30_000,
|
|
7659
|
+
probe: (topic) => {
|
|
7660
|
+
try {
|
|
7661
|
+
const session = telegram?.getSessionForTopic(topic);
|
|
7662
|
+
if (session && sessionManager.isSessionAlive(session))
|
|
7663
|
+
return { reachable: true };
|
|
7664
|
+
const stuckMs = _spawningTopicsRegistryRef?.stuckSinceMs(topic);
|
|
7665
|
+
if (stuckMs !== undefined && stuckMs >= _stuckSpawnMs)
|
|
7666
|
+
return { reachable: false, reason: 'stuck-spawn' };
|
|
7667
|
+
return { reachable: true }; // next inbound auto-spawns (self-heal)
|
|
7668
|
+
}
|
|
7669
|
+
catch {
|
|
7670
|
+
return { reachable: true }; // fail-safe: never false-orphan on a probe error
|
|
7671
|
+
}
|
|
7672
|
+
},
|
|
7673
|
+
surface: (item) => {
|
|
7674
|
+
void telegram
|
|
7675
|
+
?.createAttentionItem({
|
|
7676
|
+
id: item.key,
|
|
7677
|
+
title: item.rolledUp ? 'Some conversations may be unreachable' : 'A conversation may be unreachable',
|
|
7678
|
+
summary: item.reason,
|
|
7679
|
+
category: 'reachability',
|
|
7680
|
+
priority: 'NORMAL',
|
|
7681
|
+
sourceContext: 'topic-reachability',
|
|
7682
|
+
})
|
|
7683
|
+
.catch(() => { });
|
|
7684
|
+
},
|
|
7685
|
+
pressureCritical: () => {
|
|
7686
|
+
try {
|
|
7687
|
+
return sessionManager.pressureTier?.() === 'critical';
|
|
7688
|
+
}
|
|
7689
|
+
catch {
|
|
7690
|
+
return false;
|
|
7691
|
+
}
|
|
7692
|
+
},
|
|
7693
|
+
emergencyStopActive: () => false, // wired to the operator-halt signal in a follow-up
|
|
7694
|
+
})
|
|
7695
|
+
: null;
|
|
7696
|
+
if (topicReachabilityVerifier) {
|
|
7697
|
+
const _trvTick = setInterval(() => {
|
|
7698
|
+
try {
|
|
7699
|
+
topicReachabilityVerifier.tick();
|
|
7700
|
+
}
|
|
7701
|
+
catch {
|
|
7702
|
+
/* @silent-fallback-ok: a verifier tick error must never crash the server. */
|
|
7703
|
+
}
|
|
7704
|
+
}, 15_000);
|
|
7705
|
+
_trvTick.unref?.();
|
|
7706
|
+
guardRegistry.register('monitoring.topicReachabilityVerifier.enabled', () => ({
|
|
7707
|
+
enabled: true,
|
|
7708
|
+
...topicReachabilityVerifier.status(),
|
|
7709
|
+
}));
|
|
7710
|
+
}
|
|
7635
7711
|
sessionManager.on('sessionReaped', (e) => {
|
|
7712
|
+
// ── F7: schedule a post-grace reachability verify for the affected topic. Both a
|
|
7713
|
+
// terminal kill AND a recovery-bounce schedule one (a recovery whose respawn
|
|
7714
|
+
// wedges is exactly the orphan to catch); the verifier's grace lets a normal
|
|
7715
|
+
// self-heal land first, so a healthy bounce never surfaces. Pure signal. ──
|
|
7716
|
+
if (topicReachabilityVerifier) {
|
|
7717
|
+
try {
|
|
7718
|
+
const rawTrv = telegram?.getTopicForSession(e.session.tmuxSession);
|
|
7719
|
+
const trvTopic = rawTrv == null ? null : Number.isFinite(Number(rawTrv)) ? Number(rawTrv) : null;
|
|
7720
|
+
if (trvTopic != null)
|
|
7721
|
+
topicReachabilityVerifier.recordMutation(trvTopic);
|
|
7722
|
+
}
|
|
7723
|
+
catch {
|
|
7724
|
+
/* @silent-fallback-ok: a verifier trigger error must never affect the reap. */
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7636
7727
|
// ── GAP-B Part B (spec: autonomous-registration-guarantee.md) — the
|
|
7637
7728
|
// commitment-evidence BACKSTOP for an UNregistered-but-working autonomous
|
|
7638
7729
|
// run. Computed ONCE here (cheap, fail-open) so its verdict feeds BOTH the
|