instar 1.2.62 → 1.2.63

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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiqDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAy4LtE;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;AAuQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiqDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+6LtE;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"}
@@ -4750,31 +4750,67 @@ export async function startServer(options) {
4750
4750
  // docs/specs/rate-limit-sentinel.md.
4751
4751
  const { RateLimitSentinel } = await import('../monitoring/RateLimitSentinel.js');
4752
4752
  const getClaudeSessionIdForName = (sessionName) => sessionManager.listRunningSessions().find(s => s.tmuxSession === sessionName)?.claudeSessionId;
4753
- // Neutral "continue" nudge NOT the compaction-resume payload (which would
4754
- // falsely tell the agent its memory was reset). Topic-tagged so InputGuard
4755
- // accepts it.
4756
- const rateLimitResume = async (sessionName) => {
4757
- if (!sessionManager.isSessionAlive(sessionName))
4758
- return false;
4759
- const topicId = telegram?.getTopicForSession(sessionName);
4760
- if (topicId == null)
4761
- return false;
4762
- const tagged = `[telegram:${topicId}] The temporary server throttle should have cleared — please continue where you left off.`;
4763
- return sessionManager.injectMessage(sessionName, tagged);
4764
- };
4765
- // Route a user-facing notice for the session's topic (Telegram).
4766
- const rateLimitNotify = async (sessionName, text) => {
4767
- const topicId = telegram?.getTopicForSession(sessionName);
4768
- if (topicId == null)
4769
- return;
4770
- const resp = await fetch(`http://localhost:${config.port}/telegram/reply/${topicId}`, {
4771
- method: 'POST',
4772
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.authToken}` },
4773
- body: JSON.stringify({ text }),
4774
- });
4775
- if (!resp.ok)
4776
- throw new Error(`rate-limit notify failed: ${resp.status}`);
4753
+ // Recovery-reachability audit trail. Both recovery paths below ALWAYS leave
4754
+ // a record: a recovery-reached/recovery-unreachable line in the sentinel
4755
+ // audit log, and — when delivery to the user fails entirely — an entry in
4756
+ // .instar/sentinel-alerts.json so the dashboard surfaces it even without
4757
+ // Telegram. The defining bug this closes: a non-topic-bound session (e.g. a
4758
+ // developer's interactive Claude Code window) used to make both recovery
4759
+ // paths silently no-op, so the throttle never recovered and nothing reached
4760
+ // the user. Reachability is now unconditional — see the Sentinel
4761
+ // Reachability spec.
4762
+ const rlReachLogPath = path.join(config.stateDir, '..', 'logs', 'sentinel-events.jsonl');
4763
+ const rlAlertsPath = path.join(config.stateDir, 'sentinel-alerts.json');
4764
+ const recordRecovery = (kind, sessionName, detail, fallbackTried) => {
4765
+ const entry = { timestamp: new Date().toISOString(), kind, sentinel: 'rate-limit', sessionName, detail, fallbackTried };
4766
+ console.log(`[sentinel:${kind}] rate-limit/${sessionName} ${detail}`);
4767
+ try {
4768
+ fs.appendFileSync(rlReachLogPath, JSON.stringify(entry) + '\n');
4769
+ }
4770
+ catch { /* best-effort */ }
4771
+ if (kind === 'recovery-unreachable') {
4772
+ // Append-only alert log the dashboard reads when Telegram can't be reached.
4773
+ try {
4774
+ let alerts = [];
4775
+ if (fs.existsSync(rlAlertsPath)) {
4776
+ try {
4777
+ alerts = JSON.parse(fs.readFileSync(rlAlertsPath, 'utf-8'));
4778
+ }
4779
+ catch {
4780
+ alerts = [];
4781
+ }
4782
+ if (!Array.isArray(alerts))
4783
+ alerts = [];
4784
+ }
4785
+ alerts.push(entry);
4786
+ fs.writeFileSync(rlAlertsPath, JSON.stringify(alerts.slice(-200), null, 2));
4787
+ }
4788
+ catch { /* best-effort */ }
4789
+ }
4777
4790
  };
4791
+ // Reachability deps (extracted to sentinelWiring.buildRateLimitRecoveryDeps
4792
+ // so the topic / lifeline / audit branching is unit-testable). Topic-bound
4793
+ // sessions get a topic-tagged nudge + topic notice; non-topic-bound sessions
4794
+ // (e.g. an interactive dev window) get a trusted internal nudge + a lifeline
4795
+ // notice; if no channel is reachable, a recovery-unreachable audit event is
4796
+ // recorded instead of a silent no-op.
4797
+ const { buildRateLimitRecoveryDeps } = await import('../monitoring/sentinelWiring.js');
4798
+ const { resumeFn: rateLimitResume, notifyFn: rateLimitNotify } = buildRateLimitRecoveryDeps({
4799
+ isSessionAlive: (name) => sessionManager.isSessionAlive(name),
4800
+ injectTopicNudge: (name, topicId, text) => sessionManager.injectMessage(name, `[telegram:${topicId}] ${text}`),
4801
+ injectInternalNudge: (name, text) => sessionManager.injectInternalMessage(name, text, 'sentinel-recovery'),
4802
+ getTopicForSession: (name) => telegram?.getTopicForSession(name),
4803
+ getLifelineTopicId: () => telegram?.getLifelineTopicId?.(),
4804
+ deliverNotice: async (topicId, text) => {
4805
+ const resp = await fetch(`http://localhost:${config.port}/telegram/reply/${topicId}`, {
4806
+ method: 'POST',
4807
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.authToken}` },
4808
+ body: JSON.stringify({ text }),
4809
+ });
4810
+ return resp.ok;
4811
+ },
4812
+ recordRecovery,
4813
+ });
4778
4814
  const rlsCfg = config.monitoring?.rateLimitSentinel ?? { enabled: true };
4779
4815
  const rateLimitSentinel = new RateLimitSentinel({
4780
4816
  resumeFn: rateLimitResume,