instar 1.3.728 → 1.3.730
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 +259 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +11 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/FileClassifier.d.ts.map +1 -1
- package/dist/core/FileClassifier.js +11 -0
- package/dist/core/FileClassifier.js.map +1 -1
- package/dist/core/LocalSessionOwnershipStore.d.ts +5 -0
- package/dist/core/LocalSessionOwnershipStore.d.ts.map +1 -1
- package/dist/core/LocalSessionOwnershipStore.js +13 -0
- package/dist/core/LocalSessionOwnershipStore.js.map +1 -1
- package/dist/core/ModelSwapService.d.ts +13 -0
- package/dist/core/ModelSwapService.d.ts.map +1 -1
- package/dist/core/ModelSwapService.js +11 -0
- package/dist/core/ModelSwapService.js.map +1 -1
- package/dist/core/ModelTierEscalation.d.ts +10 -0
- package/dist/core/ModelTierEscalation.d.ts.map +1 -1
- package/dist/core/ModelTierEscalation.js +4 -0
- package/dist/core/ModelTierEscalation.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +35 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ProactiveSwapMonitor.d.ts +74 -20
- package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
- package/dist/core/ProactiveSwapMonitor.js +349 -28
- package/dist/core/ProactiveSwapMonitor.js.map +1 -1
- package/dist/core/QuotaAwareScheduler.d.ts +70 -3
- package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
- package/dist/core/QuotaAwareScheduler.js +100 -16
- package/dist/core/QuotaAwareScheduler.js.map +1 -1
- package/dist/core/SessionBuildContextStore.d.ts +20 -0
- package/dist/core/SessionBuildContextStore.d.ts.map +1 -1
- package/dist/core/SessionBuildContextStore.js +20 -3
- package/dist/core/SessionBuildContextStore.js.map +1 -1
- package/dist/core/SessionManager.d.ts +33 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +105 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionOwnershipRegistry.d.ts +16 -0
- package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -1
- package/dist/core/SessionOwnershipRegistry.js +13 -0
- package/dist/core/SessionOwnershipRegistry.js.map +1 -1
- package/dist/core/SessionRefresh.d.ts +116 -1
- package/dist/core/SessionRefresh.d.ts.map +1 -1
- package/dist/core/SessionRefresh.js +280 -17
- package/dist/core/SessionRefresh.js.map +1 -1
- package/dist/core/StateManager.d.ts +20 -0
- package/dist/core/StateManager.d.ts.map +1 -1
- package/dist/core/StateManager.js +72 -4
- package/dist/core/StateManager.js.map +1 -1
- package/dist/core/SwapAntiThrash.d.ts +329 -0
- package/dist/core/SwapAntiThrash.d.ts.map +1 -0
- package/dist/core/SwapAntiThrash.js +1025 -0
- package/dist/core/SwapAntiThrash.js.map +1 -0
- package/dist/core/SwapLedger.d.ts +168 -0
- package/dist/core/SwapLedger.d.ts.map +1 -0
- package/dist/core/SwapLedger.js +275 -0
- package/dist/core/SwapLedger.js.map +1 -0
- package/dist/core/SwapWorkGate.d.ts +100 -0
- package/dist/core/SwapWorkGate.d.ts.map +1 -0
- package/dist/core/SwapWorkGate.js +175 -0
- package/dist/core/SwapWorkGate.js.map +1 -0
- package/dist/core/WriteAdmission.d.ts +277 -0
- package/dist/core/WriteAdmission.d.ts.map +1 -0
- package/dist/core/WriteAdmission.js +514 -0
- package/dist/core/WriteAdmission.js.map +1 -0
- package/dist/core/WriteDomainRegistry.d.ts +132 -0
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -0
- package/dist/core/WriteDomainRegistry.js +186 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +89 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +58 -1
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +20 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +6 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +115 -3
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +3 -1
- package/upgrades/1.3.729.md +92 -0
- package/upgrades/1.3.730.md +112 -0
- package/upgrades/side-effects/standby-write-reconciliation.md +62 -0
- package/upgrades/side-effects/swap-continuity-antithrash.md +118 -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;AAgDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;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;AAu/CD,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,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2skBtE;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
|
@@ -26,6 +26,7 @@ import { SpawningTopicsRegistry } from '../core/SpawningTopicsRegistry.js';
|
|
|
26
26
|
import { TopicReachabilityVerifier } from '../monitoring/TopicReachabilityVerifier.js';
|
|
27
27
|
import { SingleInstanceLock, installReleaseHandlers } from '../core/SingleInstanceLock.js';
|
|
28
28
|
import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGate.js';
|
|
29
|
+
import { resolveAntiThrashKnobs, readingValidity } from '../core/SwapAntiThrash.js';
|
|
29
30
|
import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
|
|
30
31
|
import { PrHandLease } from '../core/PrHandLease.js';
|
|
31
32
|
import { claimSuspensionExcludesPin } from '../core/TopicClaimAnnotationStore.js';
|
|
@@ -760,6 +761,12 @@ let _quotaAwareScheduler = null;
|
|
|
760
761
|
// Subscription & Auth Standard P1.3 — pre-limit proactive swap monitor. Null
|
|
761
762
|
// until wired (requires the scheduler; gated behind subscriptionPool.proactiveSwap).
|
|
762
763
|
let _proactiveSwapMonitor = null;
|
|
764
|
+
// swap-continuity-antithrash §3/§4 — the anti-thrash spine. The ledger + engine
|
|
765
|
+
// load and hydrate UNCONDITIONALLY at boot (R4-L3: reactive rows append
|
|
766
|
+
// regardless of antiThrash.enabled, so the index is warm at a mid-run flag
|
|
767
|
+
// flip). Null only when construction itself failed (non-fatal).
|
|
768
|
+
let _swapAntiThrashEngine = null;
|
|
769
|
+
let _swapWorkGate = null;
|
|
763
770
|
async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory, userProfile, precomputedContext,
|
|
764
771
|
/** Subscription & Auth Standard P1.3 (additive): launch under this account's
|
|
765
772
|
* config home + record its id (the quota-aware account-swap mechanism).
|
|
@@ -5013,6 +5020,12 @@ export async function startServer(options) {
|
|
|
5013
5020
|
// idle-monitor's rateLimitedAtIdle hand-off. `enabled` OMITTED from defaults ⇒
|
|
5014
5021
|
// dev-agent live / dark-fleet.
|
|
5015
5022
|
idleThrottleSettleGate: resolveDevAgentGate(config.monitoring?.idleThrottleSettleGate?.enabled, config),
|
|
5023
|
+
// Standby-write reconciliation §3.3 (round-2 L3): per-machine
|
|
5024
|
+
// SessionBuildContextStore re-key. The id comes from the coordinator/
|
|
5025
|
+
// mesh identity via this LATE-RESOLVING getter (the mesh id is assigned
|
|
5026
|
+
// further down the boot, after SessionManager exists); identity-less
|
|
5027
|
+
// installs resolve null → the literal 'local' inside the store.
|
|
5028
|
+
buildContextMachineId: () => _meshSelfId,
|
|
5016
5029
|
});
|
|
5017
5030
|
// Wire the SAME TTL-cached SDK-credit reader PR1's routing policy uses, so
|
|
5018
5031
|
// the reroute 'auto' decision and the intelligence-funnel routing share one
|
|
@@ -14956,6 +14969,94 @@ export async function startServer(options) {
|
|
|
14956
14969
|
divergenceChecker = undefined;
|
|
14957
14970
|
}
|
|
14958
14971
|
}
|
|
14972
|
+
// ── Swap-continuity anti-thrash spine (swap-continuity-antithrash §3/§4) ──
|
|
14973
|
+
// SwapLedger + SwapAntiThrashEngine + SwapWorkGate. Constructed and
|
|
14974
|
+
// hydrated UNCONDITIONALLY (R4-L3): the ledger loads at every boot
|
|
14975
|
+
// regardless of antiThrash.enabled so the in-memory index (dwell /
|
|
14976
|
+
// reversal / breaker / frequency) is warm the moment the flag flips on
|
|
14977
|
+
// mid-run — the §7.1 half-enable trap closed for this key. All antiThrash
|
|
14978
|
+
// knobs are read LIVE per tick via the getter (the config object is
|
|
14979
|
+
// shared + mutated in place by config PATCHes).
|
|
14980
|
+
const antiThrashKnobsGetter = () => resolveAntiThrashKnobs(config.subscriptionPool?.proactiveSwap?.antiThrash, {
|
|
14981
|
+
thresholdPct: config.subscriptionPool?.proactiveSwap?.thresholdPct,
|
|
14982
|
+
tickMs: config.subscriptionPool?.proactiveSwap?.tickMs,
|
|
14983
|
+
});
|
|
14984
|
+
// swapContinuity (Piece 2, the work gate): the `enabled` KEY IS OMITTED
|
|
14985
|
+
// from shipped config on purpose — omission routes it through the
|
|
14986
|
+
// dev-agent gate (live on a dev agent, dark on the fleet; the #1001
|
|
14987
|
+
// anti-mechanism). `enabled` is RESTART-REQUIRED (§7.1 — the gate wiring
|
|
14988
|
+
// into SessionRefresh is constructor-injected); dryRun + numeric knobs
|
|
14989
|
+
// are read live per evaluation via the getters below.
|
|
14990
|
+
const swapContinuityEnabled = resolveDevAgentGate(config.subscriptionPool?.swapContinuity?.enabled, config);
|
|
14991
|
+
const swapContinuityKnobs = () => {
|
|
14992
|
+
const c = config.subscriptionPool?.swapContinuity;
|
|
14993
|
+
return {
|
|
14994
|
+
enabled: swapContinuityEnabled,
|
|
14995
|
+
dryRun: c?.dryRun ?? true,
|
|
14996
|
+
deferralCeilingMs: c?.deferralCeilingMs ?? 1_800_000,
|
|
14997
|
+
reactiveGraceMs: c?.reactiveGraceMs ?? 120_000,
|
|
14998
|
+
recheckMs: c?.recheckMs ?? 10_000,
|
|
14999
|
+
};
|
|
15000
|
+
};
|
|
15001
|
+
try {
|
|
15002
|
+
const { SwapLedger } = await import('../core/SwapLedger.js');
|
|
15003
|
+
const { SwapAntiThrashEngine, retentionBoundMs, crossKnobWarnings } = await import('../core/SwapAntiThrash.js');
|
|
15004
|
+
const { SwapWorkGate } = await import('../core/SwapWorkGate.js');
|
|
15005
|
+
const swapLedger = new SwapLedger({
|
|
15006
|
+
filePath: path.join(config.stateDir, 'state', 'swap-ledger.jsonl'),
|
|
15007
|
+
windowMs: () => retentionBoundMs(antiThrashKnobsGetter()),
|
|
15008
|
+
// Counts supplied at outage-resume time (the ledger-lost refusals the
|
|
15009
|
+
// writer itself could not record — §3.5/I5).
|
|
15010
|
+
outageRefusalCount: () => _swapAntiThrashEngine?.ledgerLostRefusalCount() ?? 0,
|
|
15011
|
+
machineId: _meshSelfId ?? 'local',
|
|
15012
|
+
logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
|
|
15013
|
+
});
|
|
15014
|
+
_swapAntiThrashEngine = new SwapAntiThrashEngine({
|
|
15015
|
+
ledger: swapLedger,
|
|
15016
|
+
getKnobs: antiThrashKnobsGetter,
|
|
15017
|
+
machineId: _meshSelfId ?? 'local',
|
|
15018
|
+
// P17: episode-deduped attention (the engine dedupes on episodeId;
|
|
15019
|
+
// the id passed here is the stable per-episode dedupe key).
|
|
15020
|
+
raiseAttention: (id, title, body) => {
|
|
15021
|
+
void telegram
|
|
15022
|
+
?.createAttentionItem({
|
|
15023
|
+
id,
|
|
15024
|
+
title,
|
|
15025
|
+
summary: body,
|
|
15026
|
+
category: 'subscription-pool',
|
|
15027
|
+
priority: 'HIGH',
|
|
15028
|
+
sourceContext: 'subscription-pool:swap-antithrash',
|
|
15029
|
+
})
|
|
15030
|
+
.catch(() => {
|
|
15031
|
+
/* @silent-fallback-ok: attention is best-effort; the ledger row is the durable trace */
|
|
15032
|
+
});
|
|
15033
|
+
},
|
|
15034
|
+
logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
|
|
15035
|
+
});
|
|
15036
|
+
_swapAntiThrashEngine.hydrate();
|
|
15037
|
+
// Cross-knob coherence warnings (§7, R4-L2 — warn-only, once at boot).
|
|
15038
|
+
// 15 min = the QuotaPoller's default cadence (pollIntervalMs ?? 15*60_000).
|
|
15039
|
+
for (const w of crossKnobWarnings(antiThrashKnobsGetter(), 15 * 60_000))
|
|
15040
|
+
console.warn(w);
|
|
15041
|
+
_swapWorkGate = new SwapWorkGate({
|
|
15042
|
+
checkSessionWorkState: (tmux) => sessionManager.checkSessionWorkState(tmux),
|
|
15043
|
+
getClaudeSessionId: (tmux) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === tmux)?.claudeSessionId ?? null,
|
|
15044
|
+
hasActiveSubagents: (id) => subagentTracker.hasActiveSubagents(id),
|
|
15045
|
+
getActiveSubagents: (id) => subagentTracker.getActiveSubagents(id).map((r) => ({
|
|
15046
|
+
agentType: r.agentType,
|
|
15047
|
+
startedAt: r.startedAt,
|
|
15048
|
+
...(r.transcriptPath ? { transcriptPath: r.transcriptPath } : {}),
|
|
15049
|
+
})),
|
|
15050
|
+
});
|
|
15051
|
+
}
|
|
15052
|
+
catch (err) {
|
|
15053
|
+
// @silent-fallback-ok: cascade-isolation — a spine construction failure
|
|
15054
|
+
// must never 503 the server; behavior degrades to pre-brake semantics
|
|
15055
|
+
// (the legacy path) and re-attempts next boot.
|
|
15056
|
+
console.warn('[instar] swap-continuity anti-thrash spine init failed (non-fatal):', err);
|
|
15057
|
+
_swapAntiThrashEngine = null;
|
|
15058
|
+
_swapWorkGate = null;
|
|
15059
|
+
}
|
|
14959
15060
|
// SessionRefresh — agent-initiated session respawn. Requires a Telegram
|
|
14960
15061
|
// adapter (v1 scope: Telegram-bound sessions only). The respawner closure
|
|
14961
15062
|
// captures `topicMemory` by reference, so even if topicMemory is wired up
|
|
@@ -14973,6 +15074,43 @@ export async function startServer(options) {
|
|
|
14973
15074
|
state,
|
|
14974
15075
|
telegram: telegramRef,
|
|
14975
15076
|
topicResumeMap: _topicResumeMap,
|
|
15077
|
+
// swap-continuity-antithrash §4.2 — the in-flight work gate, bound at
|
|
15078
|
+
// THIS primitive (the funnel every session-killing mutation flows
|
|
15079
|
+
// through). Constructor-injected on purpose: `swapContinuity.enabled`
|
|
15080
|
+
// is restart-required (§7.1); dryRun + numeric knobs read live via
|
|
15081
|
+
// getKnobs. Null when the feature is dark on this install ⇒
|
|
15082
|
+
// byte-for-byte today's behavior (unconditional kill).
|
|
15083
|
+
workGateCtx: swapContinuityEnabled && _swapWorkGate
|
|
15084
|
+
? {
|
|
15085
|
+
probe: (tmux) => _swapWorkGate.probe(tmux),
|
|
15086
|
+
getKnobs: () => {
|
|
15087
|
+
const k = swapContinuityKnobs();
|
|
15088
|
+
return { enabled: k.enabled, dryRun: k.dryRun, reactiveGraceMs: k.reactiveGraceMs, recheckMs: k.recheckMs };
|
|
15089
|
+
},
|
|
15090
|
+
recordProceeded: (args) => _swapAntiThrashEngine?.recordProceeded(args),
|
|
15091
|
+
recordInteractiveRefusal: (args) => _swapAntiThrashEngine?.recordInteractiveRefusal(args),
|
|
15092
|
+
// §4.3(2) — Q4: v1 reads the in-memory exactly-once map (set on
|
|
15093
|
+
// inbound, cleared on reply); 'unknown' = the map/ledger is
|
|
15094
|
+
// unavailable (post-restart, or exactly-once ingress dark).
|
|
15095
|
+
// `messageLedger`/`currentInboundByTopic` are declared later in
|
|
15096
|
+
// this boot function and captured by reference — every call
|
|
15097
|
+
// happens post-boot, so the binding is initialized by then.
|
|
15098
|
+
resolveUnansweredInbound: (topicId) => {
|
|
15099
|
+
if (!messageLedger || !currentInboundByTopic)
|
|
15100
|
+
return 'unknown';
|
|
15101
|
+
const dedupeKey = currentInboundByTopic.get(String(topicId));
|
|
15102
|
+
if (!dedupeKey)
|
|
15103
|
+
return 'none';
|
|
15104
|
+
const entry = messageLedger.get(dedupeKey);
|
|
15105
|
+
if (!entry || entry.inputSnapshot === null)
|
|
15106
|
+
return 'unknown';
|
|
15107
|
+
const from = entry.senderEnvelope?.username ??
|
|
15108
|
+
entry.senderEnvelope?.firstName ??
|
|
15109
|
+
(entry.senderEnvelope?.userId !== undefined ? String(entry.senderEnvelope.userId) : 'unknown sender');
|
|
15110
|
+
return { body: entry.inputSnapshot, from, at: entry.receivedAt };
|
|
15111
|
+
},
|
|
15112
|
+
}
|
|
15113
|
+
: null,
|
|
14976
15114
|
// §10.5 Slack binding — SlackAdapter satisfies SlackRefreshBinding
|
|
14977
15115
|
// structurally (getChannelForSession / removeChannelResume /
|
|
14978
15116
|
// resolveChannelForSessionFromDisk). Null ⇒ Telegram-only, unchanged.
|
|
@@ -15036,6 +15174,30 @@ export async function startServer(options) {
|
|
|
15036
15174
|
_quotaAwareScheduler = new QuotaAwareScheduler({
|
|
15037
15175
|
listAccounts: () => subscriptionPool.list(),
|
|
15038
15176
|
softThresholdPct: config.subscriptionPool?.swapSoftThresholdPct,
|
|
15177
|
+
// swap-continuity-antithrash — the anti-thrash observation + execute-time
|
|
15178
|
+
// revalidation hooks. Reactive decision behavior is untouched (I6):
|
|
15179
|
+
// these only OBSERVE reactive swaps (dwell clock-start, hop alerts,
|
|
15180
|
+
// failed rows) and revalidate PROACTIVE explicit-target calls (§3.3).
|
|
15181
|
+
antiThrash: _swapAntiThrashEngine
|
|
15182
|
+
? {
|
|
15183
|
+
readingValid: (acct, nowMs) => readingValidity(acct, nowMs, antiThrashKnobsGetter().quotaFreshnessMs).valid,
|
|
15184
|
+
getKnobs: () => {
|
|
15185
|
+
const k = antiThrashKnobsGetter();
|
|
15186
|
+
return { thresholdPct: k.thresholdPct, targetHeadroomPct: k.targetHeadroomPct, minImprovementPct: k.minImprovementPct };
|
|
15187
|
+
},
|
|
15188
|
+
resolveCurrentAccountId: (sessionName) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === sessionName)?.subscriptionAccountId ?? null,
|
|
15189
|
+
onReactiveExecuted: (a) => _swapAntiThrashEngine?.recordReactiveExecuted(a),
|
|
15190
|
+
onReactiveFailed: (a) => _swapAntiThrashEngine?.recordExecFailure({
|
|
15191
|
+
session: a.session,
|
|
15192
|
+
from: a.from,
|
|
15193
|
+
...(a.to ? { to: a.to } : {}),
|
|
15194
|
+
kind: 'reactive',
|
|
15195
|
+
errorClass: a.errorClass,
|
|
15196
|
+
nowMs: a.nowMs,
|
|
15197
|
+
}),
|
|
15198
|
+
onReactiveRateCapRefusal: (s, n) => _swapAntiThrashEngine?.noteReactiveRateCapRefusal(s, n),
|
|
15199
|
+
}
|
|
15200
|
+
: undefined,
|
|
15039
15201
|
refreshFn: async (o) => {
|
|
15040
15202
|
if (!_sessionRefresh)
|
|
15041
15203
|
return false;
|
|
@@ -15044,8 +15206,13 @@ export async function startServer(options) {
|
|
|
15044
15206
|
reason: o.reason,
|
|
15045
15207
|
configHome: o.configHome,
|
|
15046
15208
|
accountId: o.accountId,
|
|
15209
|
+
// I11: callerClass set ONLY server-internally — the scheduler is
|
|
15210
|
+
// the one caller that knows its lane (proactive funnel vs reactive).
|
|
15211
|
+
callerClass: o.callerClass,
|
|
15047
15212
|
});
|
|
15048
|
-
|
|
15213
|
+
// Object form: surfaces the structured refusal code so the
|
|
15214
|
+
// anti-thrash observation layer can classify failures (§3.1/§3.6).
|
|
15215
|
+
return r.ok ? { ok: true } : { ok: false, code: r.code };
|
|
15049
15216
|
},
|
|
15050
15217
|
onNoAlternate: (sessionName, exhaustedAccountId) => {
|
|
15051
15218
|
void telegramRef.createAttentionItem({
|
|
@@ -15127,6 +15294,18 @@ export async function startServer(options) {
|
|
|
15127
15294
|
maxSwapsPerCycle: proactiveCfg.maxSwapsPerCycle,
|
|
15128
15295
|
cooldownMs: proactiveCfg.cooldownMs,
|
|
15129
15296
|
tickMs: proactiveCfg.tickMs,
|
|
15297
|
+
// swap-continuity-antithrash §3 — the brake engine at the proactive
|
|
15298
|
+
// DECISION chokepoint. Knobs read LIVE per tick (§7.1). dryRun keeps
|
|
15299
|
+
// the legacy decision path byte-identical while the engine logs
|
|
15300
|
+
// would-refuse shadow rows (the rung-2 soak).
|
|
15301
|
+
antiThrash: _swapAntiThrashEngine
|
|
15302
|
+
? { engine: _swapAntiThrashEngine, getKnobs: antiThrashKnobsGetter }
|
|
15303
|
+
: undefined,
|
|
15304
|
+
// §4 — the in-flight work gate (proactive arm: defer, ceiling-drop).
|
|
15305
|
+
// The monitor owns the deferral lifecycle; the gate is stateless.
|
|
15306
|
+
workGate: _swapWorkGate
|
|
15307
|
+
? { probe: (s) => _swapWorkGate.probe(s), getContinuity: swapContinuityKnobs }
|
|
15308
|
+
: undefined,
|
|
15130
15309
|
logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
|
|
15131
15310
|
});
|
|
15132
15311
|
_proactiveSwapMonitor.start();
|
|
@@ -16367,6 +16546,12 @@ export async function startServer(options) {
|
|
|
16367
16546
|
* no-op (the spawn still runs; the reconciler/applier converge — FD11).
|
|
16368
16547
|
*/
|
|
16369
16548
|
let _claimOwnershipForAutonomousSpawn = null;
|
|
16549
|
+
// Standby-write reconciliation (§3.2): the pool block's ownership store is
|
|
16550
|
+
// captured here so the WriteAdmission constructed just before AgentServer
|
|
16551
|
+
// can boot-warm its ownership index from it + register the interface-level
|
|
16552
|
+
// onCommit hook. Stays null on pool-dark installs (the index warms empty;
|
|
16553
|
+
// scoped domains collapse to the legacy lease boolean anyway).
|
|
16554
|
+
let _writeAdmissionOwnershipStore = null;
|
|
16370
16555
|
try {
|
|
16371
16556
|
const meshMod = await import('../core/MeshRpc.js');
|
|
16372
16557
|
const idMod = await import('../core/MachineIdentity.js');
|
|
@@ -16568,6 +16753,7 @@ export async function startServer(options) {
|
|
|
16568
16753
|
epochFloorOf: (sk) => ownershipEpochFloor?.(sk) ?? 0,
|
|
16569
16754
|
logger: (m) => console.log(pc.dim(` ${m}`)),
|
|
16570
16755
|
});
|
|
16756
|
+
_writeAdmissionOwnershipStore = ownershipStore;
|
|
16571
16757
|
const ownReg = sessionOwnershipRegistry;
|
|
16572
16758
|
// WS1.1: expose a read-only ownership lookup to the drain's
|
|
16573
16759
|
// spawn-boundary re-check (module-scope; the drain closure is defined
|
|
@@ -19890,10 +20076,81 @@ export async function startServer(options) {
|
|
|
19890
20076
|
console.warn(pc.yellow(` Rope-health monitor init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`));
|
|
19891
20077
|
ropeHealthMonitor = null;
|
|
19892
20078
|
}
|
|
20079
|
+
// ── Standby-Write Reconciliation + Typed Refusal (docs/specs/standby-write-reconciliation.md) ──
|
|
20080
|
+
// Ownership-scoped write admission replacing the blanket lease-boolean
|
|
20081
|
+
// standby guard. Dev-gated dark (`enabled` OMITTED from defaults →
|
|
20082
|
+
// resolveDevAgentGate) + dryRun-FIRST: while dry the LEGACY blanket guard
|
|
20083
|
+
// keeps enforcing and this layer only logs would-verdicts (§9.6). The
|
|
20084
|
+
// attach below is ONE-WAY and lands BEFORE routes are wired (AgentServer
|
|
20085
|
+
// is constructed just after), so no HTTP mutating route can ever observe a
|
|
20086
|
+
// half-attached state (§3.2 pre-construction window).
|
|
20087
|
+
let writeAdmission = null;
|
|
20088
|
+
if (resolveDevAgentGate(config.multiMachine?.writeAdmission?.enabled, config)) {
|
|
20089
|
+
try {
|
|
20090
|
+
const waMod = await import('../core/WriteAdmission.js');
|
|
20091
|
+
const regMod = await import('../core/WriteDomainRegistry.js');
|
|
20092
|
+
const waMachineId = _meshSelfId ?? coordinator.identity?.machineId ?? null;
|
|
20093
|
+
const waCfg = config.multiMachine?.writeAdmission;
|
|
20094
|
+
writeAdmission = new waMod.WriteAdmission({
|
|
20095
|
+
thisMachineId: waMachineId,
|
|
20096
|
+
isReadOnly: () => state.readOnly,
|
|
20097
|
+
isPoolActive: () => state.sessionPoolActive,
|
|
20098
|
+
registry: regMod.buildWriteDomainRegistry({ machineId: waMachineId }),
|
|
20099
|
+
dryRun: waCfg?.dryRun ?? true,
|
|
20100
|
+
refusalAggregateThreshold: waCfg?.refusalAggregateThreshold ?? 5,
|
|
20101
|
+
// In-memory session→topic binding ONLY (I2 — the disk fallback is
|
|
20102
|
+
// forbidden on the admission path).
|
|
20103
|
+
resolveTopicForSession: (sessionId) => telegram?.getTopicForSession(sessionId) ?? null,
|
|
20104
|
+
// I7 — local knowledge only: we positively know the holder only when
|
|
20105
|
+
// it is us; a refusal body's leaseHolder is otherwise null.
|
|
20106
|
+
leaseHolder: () => (coordinator.holdsLease() ? waMachineId : null),
|
|
20107
|
+
raiseAttention: telegram
|
|
20108
|
+
? (item) => {
|
|
20109
|
+
void telegram.createAttentionItem({
|
|
20110
|
+
id: item.id,
|
|
20111
|
+
title: item.title,
|
|
20112
|
+
summary: item.body,
|
|
20113
|
+
category: 'write-admission',
|
|
20114
|
+
priority: 'NORMAL',
|
|
20115
|
+
sourceContext: 'write-admission',
|
|
20116
|
+
}).catch(() => { });
|
|
20117
|
+
}
|
|
20118
|
+
: undefined,
|
|
20119
|
+
logDir: path.join(config.stateDir, 'logs'),
|
|
20120
|
+
}, _writeAdmissionOwnershipStore);
|
|
20121
|
+
state.attachWriteAdmission(writeAdmission);
|
|
20122
|
+
// One-time legacy-key cleanup (§3.3): the pre-re-key shared
|
|
20123
|
+
// `session-build-context` kv entry is removed on the LEASE HOLDER only
|
|
20124
|
+
// (a standby's delete would be refused by the cluster-shared guard —
|
|
20125
|
+
// correctly). Entries self-drain via the 6h max age regardless, so a
|
|
20126
|
+
// machine that never holds the lease loses nothing.
|
|
20127
|
+
try {
|
|
20128
|
+
if (coordinator.holdsLease() && state.get('session-build-context') !== null) {
|
|
20129
|
+
state.delete('session-build-context');
|
|
20130
|
+
console.log(pc.dim(' [write-admission] legacy session-build-context kv entry cleaned up (lease holder, one-time)'));
|
|
20131
|
+
}
|
|
20132
|
+
}
|
|
20133
|
+
catch {
|
|
20134
|
+
/* @silent-fallback-ok — the legacy key is inert and self-draining (6h max age);
|
|
20135
|
+
a failed cleanup only delays its disappearance and retries at the next boot. */
|
|
20136
|
+
}
|
|
20137
|
+
console.log(pc.dim(` Write-admission: live (${writeAdmission.mode()}) — machine ${waMachineId ?? 'local'}, legacy guard ${writeAdmission.isLive ? 'subsumed' : 'still enforcing'}`));
|
|
20138
|
+
}
|
|
20139
|
+
catch (err) {
|
|
20140
|
+
// @silent-fallback-ok — reported via console.warn; init failure leaves the
|
|
20141
|
+
// layer null → route 503s + the legacy blanket guard keeps enforcing
|
|
20142
|
+
// (§5 fail-toward-today), never blocks boot.
|
|
20143
|
+
console.warn(pc.yellow(` Write-admission init failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`));
|
|
20144
|
+
writeAdmission = null;
|
|
20145
|
+
}
|
|
20146
|
+
}
|
|
20147
|
+
else {
|
|
20148
|
+
console.log(pc.dim(' Write-admission: dark (multiMachine.writeAdmission resolves off on this agent)'));
|
|
20149
|
+
}
|
|
19893
20150
|
const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
|
|
19894
20151
|
peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
|
|
19895
20152
|
} if (peers.length === 0)
|
|
19896
|
-
return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
20153
|
+
return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
19897
20154
|
// wired before the server existed; from here on inbound binds use the
|
|
19898
20155
|
// server's own store instance.
|
|
19899
20156
|
_agentServerRef = server;
|