instar 1.3.62 → 1.3.64

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.
Files changed (64) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +132 -8
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +20 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  8. package/dist/core/PostUpdateMigrator.js +22 -0
  9. package/dist/core/PostUpdateMigrator.js.map +1 -1
  10. package/dist/core/ReapGuard.d.ts +79 -0
  11. package/dist/core/ReapGuard.d.ts.map +1 -0
  12. package/dist/core/ReapGuard.js +114 -0
  13. package/dist/core/ReapGuard.js.map +1 -0
  14. package/dist/core/SessionLivenessOracle.d.ts +104 -0
  15. package/dist/core/SessionLivenessOracle.d.ts.map +1 -0
  16. package/dist/core/SessionLivenessOracle.js +201 -0
  17. package/dist/core/SessionLivenessOracle.js.map +1 -0
  18. package/dist/core/SessionManager.d.ts +90 -10
  19. package/dist/core/SessionManager.d.ts.map +1 -1
  20. package/dist/core/SessionManager.js +200 -40
  21. package/dist/core/SessionManager.js.map +1 -1
  22. package/dist/core/types.d.ts +30 -0
  23. package/dist/core/types.d.ts.map +1 -1
  24. package/dist/core/types.js.map +1 -1
  25. package/dist/monitoring/ReapLog.d.ts +51 -0
  26. package/dist/monitoring/ReapLog.d.ts.map +1 -0
  27. package/dist/monitoring/ReapLog.js +82 -0
  28. package/dist/monitoring/ReapLog.js.map +1 -0
  29. package/dist/monitoring/ReapNotifier.d.ts +66 -0
  30. package/dist/monitoring/ReapNotifier.d.ts.map +1 -0
  31. package/dist/monitoring/ReapNotifier.js +112 -0
  32. package/dist/monitoring/ReapNotifier.js.map +1 -0
  33. package/dist/monitoring/RevertDetector.d.ts +52 -0
  34. package/dist/monitoring/RevertDetector.d.ts.map +1 -0
  35. package/dist/monitoring/RevertDetector.js +219 -0
  36. package/dist/monitoring/RevertDetector.js.map +1 -0
  37. package/dist/monitoring/SessionReaper.d.ts +4 -0
  38. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  39. package/dist/monitoring/SessionReaper.js +19 -42
  40. package/dist/monitoring/SessionReaper.js.map +1 -1
  41. package/dist/monitoring/StaleSessionBackstop.d.ts +89 -0
  42. package/dist/monitoring/StaleSessionBackstop.d.ts.map +1 -0
  43. package/dist/monitoring/StaleSessionBackstop.js +176 -0
  44. package/dist/monitoring/StaleSessionBackstop.js.map +1 -0
  45. package/dist/scaffold/templates.d.ts.map +1 -1
  46. package/dist/scaffold/templates.js +5 -0
  47. package/dist/scaffold/templates.js.map +1 -1
  48. package/dist/server/AgentServer.d.ts +4 -0
  49. package/dist/server/AgentServer.d.ts.map +1 -1
  50. package/dist/server/AgentServer.js +36 -0
  51. package/dist/server/AgentServer.js.map +1 -1
  52. package/dist/server/routes.d.ts +1 -0
  53. package/dist/server/routes.d.ts.map +1 -1
  54. package/dist/server/routes.js +33 -11
  55. package/dist/server/routes.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/data/builtin-manifest.json +64 -64
  58. package/src/scaffold/templates.ts +5 -0
  59. package/upgrades/1.3.61.md +9 -14
  60. package/upgrades/1.3.62.md +43 -45
  61. package/upgrades/1.3.63.md +87 -0
  62. package/upgrades/1.3.64.md +27 -0
  63. package/upgrades/side-effects/failure-learning-revert-source.md +41 -0
  64. package/upgrades/side-effects/unified-session-lifecycle-robustness.md +241 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+RH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAksDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA6xNtE;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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+RH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAksDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAi6NtE;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"}
@@ -3880,6 +3880,54 @@ export async function startServer(options) {
3880
3880
  // WorkingMemoryAssembler is initialized after activitySentinel (see below)
3881
3881
  // so it can wire episodicMemory from the sentinel.
3882
3882
  let workingMemory;
3883
+ // ── Reap observability + notify (UNIFIED-SESSION-LIFECYCLE §P3/§P4) ──
3884
+ // Wired BEFORE the boot purge so even boot-time reaps land in the reap-log
3885
+ // and surface a notice. The awake-checker is also wired here so the boot
3886
+ // purge is lease-gated (a standby never reaps another machine's sessions).
3887
+ // (The KEEP-guard is wired later, once its tracker deps exist — that is safe:
3888
+ // the boot purge passes knownDead and bypasses the guard, and the only other
3889
+ // guard consumers (monitorTick #2 age-limit / #3 idle-zombie) cannot fire a
3890
+ // kill in the first monitoring seconds — they require multi-hour age or 15+m
3891
+ // of observed idle — long after the guard is set below.)
3892
+ const { ReapLog } = await import('../monitoring/ReapLog.js');
3893
+ const { ReapNotifier } = await import('../monitoring/ReapNotifier.js');
3894
+ const reapLog = new ReapLog(config.stateDir, () => (coordinator.enabled ? coordinator.identity?.machineId : undefined));
3895
+ const reapNotifier = new ReapNotifier({
3896
+ resolveTopic: (tmuxSession) => {
3897
+ const t = telegram?.getTopicForSession(tmuxSession);
3898
+ if (t == null)
3899
+ return null;
3900
+ const n = typeof t === 'number' ? t : Number(t);
3901
+ return Number.isFinite(n) ? n : null;
3902
+ },
3903
+ lifelineTopic: () => telegram?.getLifelineTopicId() ?? null,
3904
+ // SUMMARY tier → through the formatter/tone-gate (HTML-escaped, quiet-hours
3905
+ // aware). The notifier already coalesces, so a burst is one message.
3906
+ send: (topicId, text) => notify('SUMMARY', 'session-reap', text, topicId),
3907
+ }, {
3908
+ enabled: config.monitoring?.reapNotify?.enabled ?? true,
3909
+ coalesceWindowMs: config.monitoring?.reapNotify?.coalesceWindowMs ?? 60_000,
3910
+ });
3911
+ sessionManager.setAwakeChecker(() => !coordinator.enabled || coordinator.isAwake);
3912
+ sessionManager.on('sessionReaped', (e) => {
3913
+ reapLog.recordReaped({
3914
+ session: e.session.name,
3915
+ tmuxSession: e.session.tmuxSession,
3916
+ reason: e.reason,
3917
+ disposition: e.disposition,
3918
+ origin: e.origin,
3919
+ });
3920
+ reapNotifier.onReaped({ session: e.session, reason: e.reason, disposition: e.disposition, origin: e.origin });
3921
+ });
3922
+ sessionManager.on('reapBlocked', (e) => {
3923
+ reapLog.recordSkipped({
3924
+ session: e.session.name,
3925
+ tmuxSession: e.session.tmuxSession,
3926
+ reason: e.reason,
3927
+ skipped: e.skipped,
3928
+ origin: e.origin,
3929
+ });
3930
+ });
3883
3931
  // Fast startup purge — remove session records for dead tmux sessions BEFORE
3884
3932
  // monitoring starts. Prevents the death spiral where stale sessions overwhelm
3885
3933
  // the health endpoint (synchronous tmux has-session calls) and cause the
@@ -7721,14 +7769,15 @@ export async function startServer(options) {
7721
7769
  const n = typeof t === 'number' ? t : Number(t);
7722
7770
  return Number.isFinite(n) ? n : null;
7723
7771
  };
7724
- const sessionReaper = new SessionReaper({
7725
- listRunningSessions: () => sessionManager.listRunningSessions(),
7726
- captureOutput: (s, n) => sessionManager.captureOutput(s, n) ?? '',
7727
- hasActiveProcesses: (s) => sessionManager.hasActiveProcesses(s),
7728
- frameworkForSession: (s) => sessionManager.frameworkForSession(s),
7772
+ // Shared stateless KEEP-guard deps (UNIFIED-SESSION-LIFECYCLE §P2). Built
7773
+ // once here and used to back BOTH the SessionReaper and the ReapAuthority's
7774
+ // guard (`sessionManager.setReapGuard`), so a killer cannot end a session the
7775
+ // reaper would have kept — the same chain protects every kill path.
7776
+ const reapGuardDeps = {
7777
+ protectedSessions: () => sessionManager.getProtectedSessions(),
7729
7778
  isRecoveryActive: (session) => composedRecoveryActive(session),
7730
- isRelayLeaseActive: (id) => sessionManager.isRelayLeaseActive(id),
7731
7779
  hasPendingInjection: (s) => sessionManager.getPendingInjection(s) != null,
7780
+ isRelayLeaseActive: (id) => sessionManager.isRelayLeaseActive(id),
7732
7781
  topicBinding: _resolveTopic,
7733
7782
  // Gate I is a v1 stub (returns false): active conversation is already
7734
7783
  // covered by the relay-lease + pending-injection gates and by render
@@ -7765,7 +7814,23 @@ export async function startServer(options) {
7765
7814
  return true;
7766
7815
  return fresh(path.join(config.stateDir, 'state', 'build', 'build-state.json'));
7767
7816
  },
7768
- protectedSessions: () => sessionManager.getProtectedSessions(),
7817
+ hasActiveProcesses: (s) => sessionManager.hasActiveProcesses(s),
7818
+ };
7819
+ // Wire the ReapAuthority's KEEP-guard. minAgeMs:0 disables spawn-grace here —
7820
+ // the killers that consult this guard (age-limit #2, idle-zombie #3) already
7821
+ // gate on their own age/idle thresholds, so the reaper-specific spawn-grace
7822
+ // would otherwise delay legitimate zombie kills for young-but-stuck sessions.
7823
+ {
7824
+ const { ReapGuard } = await import('../core/ReapGuard.js');
7825
+ sessionManager.setReapGuard(new ReapGuard(reapGuardDeps, { minAgeMs: 0 }));
7826
+ }
7827
+ // (setAwakeChecker is wired earlier, before the boot purge, so the purge is
7828
+ // lease-gated — see the §P3/§P4 block above.)
7829
+ const sessionReaper = new SessionReaper({
7830
+ ...reapGuardDeps,
7831
+ listRunningSessions: () => sessionManager.listRunningSessions(),
7832
+ captureOutput: (s, n) => sessionManager.captureOutput(s, n) ?? '',
7833
+ frameworkForSession: (s) => sessionManager.frameworkForSession(s),
7769
7834
  pressure: () => {
7770
7835
  const total = _os.totalmem();
7771
7836
  const freePct = total > 0 ? (_os.freemem() / total) * 100 : 100;
@@ -7778,6 +7843,65 @@ export async function startServer(options) {
7778
7843
  audit: fileAuditSink(config.stateDir),
7779
7844
  }, config.monitoring?.sessionReaper);
7780
7845
  sessionReaper.start();
7846
+ // ── Unkillability backstop (UNIFIED-SESSION-LIFECYCLE §P5) ───────────────
7847
+ // Signal-only: raises ONE deduped Attention item (never auto-kills) when a
7848
+ // session is KEPT forever despite faking work, or is stuck indeterminate.
7849
+ {
7850
+ const { StaleSessionBackstop } = await import('../monitoring/StaleSessionBackstop.js');
7851
+ const { probeTranscript } = await import('../monitoring/transcriptProber.js');
7852
+ const { makeAttentionPoster } = await import('../monitoring/sentinelWiring.js');
7853
+ const _crypto = await import('node:crypto');
7854
+ const staleCfg = config.monitoring?.staleBackstop ?? {};
7855
+ const progressFloorBytes = staleCfg.progressFloorBytes ?? 512;
7856
+ const hash = (s) => _crypto.createHash('sha1').update(s).digest('hex').slice(0, 16);
7857
+ const backstop = new StaleSessionBackstop({
7858
+ listRunningSessions: () => sessionManager.listRunningSessions(),
7859
+ probeLiveness: (names) => sessionManager.probeLivenessBatch(names),
7860
+ snapshot: (session) => {
7861
+ const framework = session.framework ?? sessionManager.frameworkForSession(session.tmuxSession) ?? 'claude-code';
7862
+ const sessionId = framework === 'claude-code' ? (session.claudeSessionId ?? '') : '';
7863
+ const tp = probeTranscript({ framework, sessionId, projectDir: '' });
7864
+ // Tail hash: read the last progressFloorBytes so a heartbeat-byte append
7865
+ // (same tail) is NOT counted as a meaningful advance.
7866
+ let tailHash = null;
7867
+ if (tp.resolved && tp.path) {
7868
+ try {
7869
+ const fd = fs.openSync(tp.path, 'r');
7870
+ try {
7871
+ const start = Math.max(0, tp.size - progressFloorBytes);
7872
+ const len = tp.size - start;
7873
+ const buf = Buffer.alloc(len);
7874
+ fs.readSync(fd, buf, 0, len, start);
7875
+ tailHash = hash(buf.toString('utf-8'));
7876
+ }
7877
+ finally {
7878
+ fs.closeSync(fd);
7879
+ }
7880
+ }
7881
+ catch {
7882
+ // @silent-fallback-ok — an unreadable transcript tail just means
7883
+ // "no meaningful-advance signal this tick"; the backstop treats a
7884
+ // null tail as ambiguous (never as progress), which is safe.
7885
+ tailHash = null;
7886
+ }
7887
+ }
7888
+ const frame = sessionManager.captureOutput(session.tmuxSession, 8) ?? '';
7889
+ return {
7890
+ transcriptResolved: tp.resolved,
7891
+ transcriptSize: tp.size,
7892
+ transcriptTailHash: tailHash,
7893
+ mainProcessActive: sessionManager.hasActiveProcesses(session.tmuxSession),
7894
+ idleStateToken: hash(frame),
7895
+ };
7896
+ },
7897
+ raiseAttention: makeAttentionPoster({ port: config.port, authToken: config.authToken ?? '' }),
7898
+ setLongIndeterminate: (id, isLong) => sessionManager.markLongIndeterminate(id, isLong),
7899
+ }, staleCfg);
7900
+ backstop.start();
7901
+ if (staleCfg.enabled !== false) {
7902
+ console.log(pc.green(' Unkillability backstop enabled (§P5 — signal-only, never auto-kills)'));
7903
+ }
7904
+ }
7781
7905
  if (config.monitoring?.sessionReaper?.enabled) {
7782
7906
  console.log(pc.green(config.monitoring.sessionReaper.dryRun === false
7783
7907
  ? ' SessionReaper enabled (idle-session reaper — LIVE)'
@@ -7930,7 +8054,7 @@ export async function startServer(options) {
7930
8054
  }
7931
8055
  }
7932
8056
  }
7933
- 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, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, 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, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, unjustifiedStopGate, stopGateDb, stopNotifier });
8057
+ 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, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, leaseTransport, 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, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, reapLog, unjustifiedStopGate, stopGateDb, stopNotifier });
7934
8058
  // Boot-recovery (tunnel-failure-resilience spec Part 6): if the agent
7935
8059
  // died mid-relay-episode, the persisted tunnel.json carries
7936
8060
  // rotationPending=true. Rotate the dashboard PIN + authToken BEFORE