instar 1.2.82 → 1.3.0

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 (62) hide show
  1. package/dist/commands/init.js +14 -3
  2. package/dist/commands/init.js.map +1 -1
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +143 -1
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  7. package/dist/config/ConfigDefaults.js +23 -0
  8. package/dist/config/ConfigDefaults.js.map +1 -1
  9. package/dist/core/PostUpdateMigrator.d.ts +2 -1
  10. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  11. package/dist/core/PostUpdateMigrator.js +268 -3
  12. package/dist/core/PostUpdateMigrator.js.map +1 -1
  13. package/dist/core/SessionManager.d.ts +43 -0
  14. package/dist/core/SessionManager.d.ts.map +1 -1
  15. package/dist/core/SessionManager.js +123 -24
  16. package/dist/core/SessionManager.js.map +1 -1
  17. package/dist/core/installCodexHooks.d.ts.map +1 -1
  18. package/dist/core/installCodexHooks.js +3 -2
  19. package/dist/core/installCodexHooks.js.map +1 -1
  20. package/dist/core/types.d.ts +26 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/core/types.js.map +1 -1
  23. package/dist/monitoring/SessionReaper.d.ts +153 -0
  24. package/dist/monitoring/SessionReaper.d.ts.map +1 -0
  25. package/dist/monitoring/SessionReaper.js +376 -0
  26. package/dist/monitoring/SessionReaper.js.map +1 -0
  27. package/dist/monitoring/TokenLedger.d.ts +12 -0
  28. package/dist/monitoring/TokenLedger.d.ts.map +1 -1
  29. package/dist/monitoring/TokenLedger.js +22 -0
  30. package/dist/monitoring/TokenLedger.js.map +1 -1
  31. package/dist/monitoring/transcriptProber.d.ts +44 -0
  32. package/dist/monitoring/transcriptProber.d.ts.map +1 -0
  33. package/dist/monitoring/transcriptProber.js +57 -0
  34. package/dist/monitoring/transcriptProber.js.map +1 -0
  35. package/dist/scaffold/templates.d.ts.map +1 -1
  36. package/dist/scaffold/templates.js +6 -0
  37. package/dist/scaffold/templates.js.map +1 -1
  38. package/dist/server/AgentServer.d.ts +3 -0
  39. package/dist/server/AgentServer.d.ts.map +1 -1
  40. package/dist/server/AgentServer.js +1 -0
  41. package/dist/server/AgentServer.js.map +1 -1
  42. package/dist/server/routes.d.ts +3 -0
  43. package/dist/server/routes.d.ts.map +1 -1
  44. package/dist/server/routes.js +20 -2
  45. package/dist/server/routes.js.map +1 -1
  46. package/dist/server/stopGate.d.ts +8 -2
  47. package/dist/server/stopGate.d.ts.map +1 -1
  48. package/dist/server/stopGate.js +42 -2
  49. package/dist/server/stopGate.js.map +1 -1
  50. package/package.json +1 -1
  51. package/playbook-scripts/build-state.py +39 -1
  52. package/scripts/analyze-release.js +16 -8
  53. package/scripts/generate-builtin-manifest.cjs +2 -1
  54. package/src/data/builtin-manifest.json +76 -67
  55. package/src/scaffold/templates.ts +6 -0
  56. package/src/templates/hooks/build-stop-hook.sh +62 -0
  57. package/src/templates/hooks/settings-template.json +10 -0
  58. package/upgrades/1.2.83.md +26 -0
  59. package/upgrades/1.3.0.md +27 -0
  60. package/upgrades/side-effects/build-stop-hook-session-scoping.md +133 -0
  61. package/upgrades/side-effects/fresh-session-stop-gate-shadow-wiring.md +35 -0
  62. package/upgrades/side-effects/session-reaper.md +42 -0
@@ -4874,6 +4874,12 @@ export async function startServer(options) {
4874
4874
  // is OFF for Telegram by default and, when enabled, coalesces into ONE
4875
4875
  // consolidated message to the existing system topic. No new-topic-per-event.
4876
4876
  // Spec: docs/specs/silently-stopped-trio.md.
4877
+ //
4878
+ // Captured out of the trio block so the SessionReaper's recovery veto can
4879
+ // compose socket + silence in too (SESSION-REAPER-SPEC §4 "compose, don't
4880
+ // replace"). undefined when the corresponding sentinel is disabled.
4881
+ let socketRecoveryActive;
4882
+ let silenceRecoveryActive;
4877
4883
  {
4878
4884
  const { SocketDisconnectSentinel } = await import('../monitoring/SocketDisconnectSentinel.js');
4879
4885
  const { ActiveWorkSilenceSentinel } = await import('../monitoring/ActiveWorkSilenceSentinel.js');
@@ -4926,6 +4932,7 @@ export async function startServer(options) {
4926
4932
  socketSentinel.on('recovered', (n) => notifier.record('recovered', 'socket-disconnect', n));
4927
4933
  socketSentinel.on('recovery-error', (e) => notifier.record('recovery-error', 'socket-disconnect', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
4928
4934
  socketSentinel.start();
4935
+ socketRecoveryActive = (s) => socketSentinel.isRecoveryActive(s);
4929
4936
  console.log(pc.green(' SocketDisconnectSentinel enabled (connection-drop recovery)'));
4930
4937
  }
4931
4938
  const silenceCfg = config.monitoring?.activeWorkSilenceSentinel ?? { enabled: true };
@@ -4939,11 +4946,23 @@ export async function startServer(options) {
4939
4946
  silenceSentinel.on('recovered', (n) => notifier.record('recovered', 'active-silence', n));
4940
4947
  silenceSentinel.on('nudge-error', (e) => notifier.record('nudge-error', 'active-silence', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
4941
4948
  silenceSentinel.start();
4949
+ silenceRecoveryActive = (s) => silenceSentinel.isRecoveryActive(s);
4942
4950
  console.log(pc.green(telegramEscalation
4943
4951
  ? ' ActiveWorkSilenceSentinel enabled (silent-freeze watchdog — Telegram escalation ON, consolidated)'
4944
4952
  : ' ActiveWorkSilenceSentinel enabled (silent-freeze watchdog — logs only, Telegram escalation OFF)'));
4945
4953
  }
4946
4954
  }
4955
+ // Recompose the zombie-kill veto to include ALL four recovery sentinels now
4956
+ // that socket + silence exist (the interim set above covered only compaction
4957
+ // + rate-limit, before those two were constructed). This single composed
4958
+ // predicate is the superset — it drops none — and is reused as the
4959
+ // SessionReaper's recovery gate (G) so the reaper never kills a session any
4960
+ // sentinel is reviving. SESSION-REAPER-SPEC §4 "compose, don't replace".
4961
+ const composedRecoveryActive = (session) => compactionSentinel.isRecoveryActive(session.tmuxSession) ||
4962
+ rateLimitSentinel.isRecoveryActive(session.tmuxSession) ||
4963
+ (socketRecoveryActive?.(session.tmuxSession) ?? false) ||
4964
+ (silenceRecoveryActive?.(session.tmuxSession) ?? false);
4965
+ sessionManager.setActiveRecoveryChecker(composedRecoveryActive);
4947
4966
  // Trigger 1: PreCompact hook event — report to sentinel.
4948
4967
  hookEventReceiver.on('PreCompact', () => {
4949
4968
  // Delay to let compaction + recovery hooks finish
@@ -6779,6 +6798,50 @@ export async function startServer(options) {
6779
6798
  const { OutboundDedupGate } = await import('../core/OutboundDedupGate.js');
6780
6799
  const outboundDedupGate = new OutboundDedupGate();
6781
6800
  console.log(pc.green(' Outbound dedup gate: active (word-3gram Jaccard, threshold 0.7, 5min window)'));
6801
+ // Unjustified Stop Gate — observe-only by default. The routes and authority
6802
+ // already fail open; constructing both pieces here is what makes the Stop
6803
+ // router produce real shadow-mode telemetry instead of a dark endpoint.
6804
+ let unjustifiedStopGate;
6805
+ let stopGateDb;
6806
+ {
6807
+ const { configureStopGateState } = await import('../server/stopGate.js');
6808
+ const modeFilePath = path.join(config.stateDir, 'server-data', 'stop-gate-mode.json');
6809
+ try {
6810
+ const { StopGateDb } = await import('../core/StopGateDb.js');
6811
+ stopGateDb = new StopGateDb({
6812
+ dbPath: path.join(config.stateDir, 'server-data', 'stop-gate.db'),
6813
+ });
6814
+ }
6815
+ catch (err) {
6816
+ DegradationReporter.getInstance().report({
6817
+ feature: 'unjustifiedStopGate.db',
6818
+ primary: 'SQLite decision log for Stop-gate evaluations',
6819
+ fallback: 'fail-open → no Stop-gate persistence',
6820
+ reason: err instanceof Error ? err.message : String(err),
6821
+ impact: 'Stop events are allowed and not recorded until the database opens.',
6822
+ });
6823
+ }
6824
+ if (sharedIntelligence && stopGateDb) {
6825
+ try {
6826
+ const { UnjustifiedStopGate } = await import('../core/UnjustifiedStopGate.js');
6827
+ unjustifiedStopGate = new UnjustifiedStopGate({ intelligence: sharedIntelligence });
6828
+ }
6829
+ catch (err) {
6830
+ DegradationReporter.getInstance().report({
6831
+ feature: 'unjustifiedStopGate.authority',
6832
+ primary: 'LLM authority for unjustified Stop-event detection',
6833
+ fallback: 'fail-open → allow',
6834
+ reason: err instanceof Error ? err.message : String(err),
6835
+ impact: 'Stop events are allowed until the authority can initialize.',
6836
+ });
6837
+ }
6838
+ }
6839
+ const activeMode = configureStopGateState({
6840
+ modeFilePath,
6841
+ defaultMode: unjustifiedStopGate && stopGateDb ? 'shadow' : 'off',
6842
+ });
6843
+ console.log(pc.green(` Unjustified Stop Gate: ${activeMode}${unjustifiedStopGate && stopGateDb ? ' (authority + SQLite wired)' : ' (degraded, fail-open)'}`));
6844
+ }
6782
6845
  // Response Review Pipeline (Coherence Gate) — evaluates agent responses before delivery.
6783
6846
  // Prefers the shared IntelligenceProvider (subscription-compatible) so the gate works
6784
6847
  // even without ANTHROPIC_API_KEY. Falls back to direct Anthropic API if a key is set
@@ -7193,7 +7256,86 @@ export async function startServer(options) {
7193
7256
  },
7194
7257
  });
7195
7258
  }
7196
- 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, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, 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, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, workingMemory, taskFlowRegistry, threadlineFlowBridge });
7259
+ // ── SessionReaper (SESSION-REAPER-SPEC) ──────────────────────────────
7260
+ // Pressure-aware reaper of idle-but-alive sessions. Ships OFF + dry-run by
7261
+ // default; the classifier's positive-evidence + confidence-contract is what
7262
+ // guarantees it never reaps a working session. Reuses composedRecoveryActive
7263
+ // (gate G) so it defers to every recovery sentinel. Pressure is freemem-tiered
7264
+ // for v1 (advisory; spawn-denial-primary is a tracked follow-up) — and note
7265
+ // an over-eager tier can only reap a GENUINELY-idle session sooner, never a
7266
+ // working one (the classifier protects working sessions regardless of tier).
7267
+ const { SessionReaper, fileAuditSink } = await import('../monitoring/SessionReaper.js');
7268
+ const _os = await import('node:os');
7269
+ const _resolveTopic = (tmuxSession) => {
7270
+ const t = telegram?.getTopicForSession(tmuxSession);
7271
+ if (t == null)
7272
+ return null;
7273
+ const n = typeof t === 'number' ? t : Number(t);
7274
+ return Number.isFinite(n) ? n : null;
7275
+ };
7276
+ const sessionReaper = new SessionReaper({
7277
+ listRunningSessions: () => sessionManager.listRunningSessions(),
7278
+ captureOutput: (s, n) => sessionManager.captureOutput(s, n) ?? '',
7279
+ hasActiveProcesses: (s) => sessionManager.hasActiveProcesses(s),
7280
+ frameworkForSession: (s) => sessionManager.frameworkForSession(s),
7281
+ isRecoveryActive: (session) => composedRecoveryActive(session),
7282
+ isRelayLeaseActive: (id) => sessionManager.isRelayLeaseActive(id),
7283
+ hasPendingInjection: (s) => sessionManager.getPendingInjection(s) != null,
7284
+ topicBinding: _resolveTopic,
7285
+ // Gate I is a v1 stub (returns false): active conversation is already
7286
+ // covered by the relay-lease + pending-injection gates and by render
7287
+ // stasis (a session being talked to is not render-static for the full
7288
+ // hysteresis+threshold window). Promoting to a real message-recency
7289
+ // query is a tracked tuning follow-up.
7290
+ recentUserMessage: () => false,
7291
+ activeCommitmentForTopic: (topicId) => {
7292
+ try {
7293
+ return commitmentTracker.getActive().some(c => c.topicId === topicId);
7294
+ }
7295
+ catch {
7296
+ return true;
7297
+ } // cannot tell → protect
7298
+ },
7299
+ activeSubagentCount: (csid) => {
7300
+ try {
7301
+ return csid ? subagentTracker.getActiveSubagents(csid).length : 0;
7302
+ }
7303
+ catch {
7304
+ return 1;
7305
+ } // cannot tell → protect
7306
+ },
7307
+ buildOrAutonomousActive: (topicId) => {
7308
+ const fresh = (p) => {
7309
+ try {
7310
+ return fs.existsSync(p) && (Date.now() - fs.statSync(p).mtimeMs) < 30 * 60_000;
7311
+ }
7312
+ catch {
7313
+ return false;
7314
+ }
7315
+ };
7316
+ if (topicId != null && fresh(path.join(config.stateDir, 'autonomous', `${topicId}.local.md`)))
7317
+ return true;
7318
+ return fresh(path.join(config.stateDir, 'state', 'build', 'build-state.json'));
7319
+ },
7320
+ protectedSessions: () => sessionManager.getProtectedSessions(),
7321
+ pressure: () => {
7322
+ const total = _os.totalmem();
7323
+ const freePct = total > 0 ? (_os.freemem() / total) * 100 : 100;
7324
+ const tier = freePct < 5 ? 'critical' : freePct < 12 ? 'moderate' : 'normal';
7325
+ return { tier, inputs: { freePct: Math.round(freePct * 10) / 10 } };
7326
+ },
7327
+ terminate: (id, reason) => sessionManager.terminateSession(id, reason),
7328
+ markReaping: (id) => sessionManager.markReaping(id),
7329
+ clearReaping: (id) => sessionManager.clearReaping(id),
7330
+ audit: fileAuditSink(config.stateDir),
7331
+ }, config.monitoring?.sessionReaper);
7332
+ sessionReaper.start();
7333
+ if (config.monitoring?.sessionReaper?.enabled) {
7334
+ console.log(pc.green(config.monitoring.sessionReaper.dryRun === false
7335
+ ? ' SessionReaper enabled (idle-session reaper — LIVE)'
7336
+ : ' SessionReaper enabled (idle-session reaper — dry-run, logs only)'));
7337
+ }
7338
+ 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, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, 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, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, unjustifiedStopGate, stopGateDb });
7197
7339
  // Boot-recovery (tunnel-failure-resilience spec Part 6): if the agent
7198
7340
  // died mid-relay-episode, the persisted tunnel.json carries
7199
7341
  // rotationPending=true. Rotate the dashboard PIN + authToken BEFORE