instar 1.1.3 → 1.2.1

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.
@@ -7285,6 +7285,35 @@ export function createRoutes(ctx) {
7285
7285
  });
7286
7286
  // ── Secret Drop (secure secret submission) ─────────────────────
7287
7287
  const secretDrop = new SecretDrop(ctx.config.projectName || 'Agent');
7288
+ // Stuck-consumer hardening: when an agent's consumer chain fails to
7289
+ // claim a submitted secret within the grace window, route a visible
7290
+ // cue to the bound topic so the operator hears about the failure
7291
+ // instead of silently waiting on a value that never arrives. Added
7292
+ // 2026-05-20 in response to the topic-10873 lost-SMS-code incident.
7293
+ secretDrop.onStuckConsumer((event) => {
7294
+ if (!event.topicId || !ctx.sessionManager)
7295
+ return;
7296
+ const topicId = event.topicId;
7297
+ const systemMsg = `[secret-drop-stuck] Secret "${event.label}" was submitted but has not been consumed by an agent process. ` +
7298
+ `If you weren't expecting this, the consumer may have hit a bug. ` +
7299
+ `The submission will auto-clean in ~${event.minutesUntilCleanup} minute(s). ` +
7300
+ `To retry, re-read it (non-destructive): curl -s -X POST -H "Authorization: Bearer $AUTH" http://localhost:${ctx.config.port}/secrets/retrieve/${event.token}.`;
7301
+ try {
7302
+ const sdRegistryPath = path.join(ctx.config.stateDir, 'topic-session-registry.json');
7303
+ let targetSession = null;
7304
+ if (fs.existsSync(sdRegistryPath)) {
7305
+ const registry = JSON.parse(fs.readFileSync(sdRegistryPath, 'utf-8'));
7306
+ targetSession = registry.topicToSession?.[String(topicId)] ?? null;
7307
+ }
7308
+ if (targetSession && ctx.sessionManager.isSessionAlive(targetSession)) {
7309
+ ctx.sessionManager.injectPasteNotification(targetSession, `<system-reminder>${systemMsg}</system-reminder>`);
7310
+ }
7311
+ }
7312
+ catch (err) {
7313
+ // @silent-fallback-ok — the stuck-consumer signal is best-effort
7314
+ console.error('[secret-drop] stuck-consumer notify failed:', err instanceof Error ? err.message : String(err));
7315
+ }
7316
+ });
7288
7317
  /** Build a browser-clickable tunnel URL for a secret drop */
7289
7318
  function secretDropUrl(token) {
7290
7319
  const localUrl = `/secrets/drop/${token}`;
@@ -7391,7 +7420,9 @@ export function createRoutes(ctx) {
7391
7420
  const fieldCount = Object.keys(submission.values).length;
7392
7421
  const fieldNames = Object.keys(submission.values).join(', ');
7393
7422
  const systemMsg = `[secret-drop-received] Secret "${submission.label}" was just submitted (${fieldCount} field${fieldCount !== 1 ? 's' : ''}: ${fieldNames}). ` +
7394
- `Retrieve it with: curl -s -X POST -H "Authorization: Bearer $AUTH" http://localhost:${ctx.config.port}/secrets/retrieve/${req.params.token} | Then acknowledge receipt to the user conversationally via Telegram topic ${topicId}.`;
7423
+ `Retrieve (non-destructive — safe to retry on parse error): curl -s -X POST -H "Authorization: Bearer $AUTH" http://localhost:${ctx.config.port}/secrets/retrieve/${req.params.token}. ` +
7424
+ `When you have successfully handed off the value, consume it: append ?consume=true to the same URL. ` +
7425
+ `Then acknowledge receipt to the user conversationally via Telegram topic ${topicId}.`;
7395
7426
  const sdRegistryPath = path.join(ctx.config.stateDir, 'topic-session-registry.json');
7396
7427
  let targetSession = null;
7397
7428
  try {
@@ -7530,15 +7561,25 @@ export function createRoutes(ctx) {
7530
7561
  }
7531
7562
  res.json({ ok: true });
7532
7563
  });
7533
- // Retrieve a received secret (agent-facing, requires auth)
7534
- // This is for polling-based retrieval — the secret is returned once and deleted.
7564
+ // Retrieve a received secret (agent-facing, requires auth).
7565
+ //
7566
+ // Non-destructive by default — repeated calls return the same value
7567
+ // until the in-memory cleanup timer fires (~5 min). Pass `?consume=true`
7568
+ // for the legacy one-shot semantics. Rationale: a buggy consumer that
7569
+ // dropped the response value used to lose the secret with no recovery;
7570
+ // non-destructive default lets the caller retry. See
7571
+ // docs/specs/secret-drop-hardening.md for the full rationale.
7535
7572
  router.post('/secrets/retrieve/:token', (req, res) => {
7536
- const submission = secretDrop.getReceived(req.params.token);
7573
+ const consumeFlag = req.query.consume;
7574
+ const consume = consumeFlag === 'true' || consumeFlag === '1';
7575
+ const submission = consume
7576
+ ? secretDrop.consumeReceived(req.params.token)
7577
+ : secretDrop.peekReceived(req.params.token);
7537
7578
  if (!submission) {
7538
7579
  res.status(404).json({ error: 'No submission found for this token' });
7539
7580
  return;
7540
7581
  }
7541
- res.json(submission);
7582
+ res.json({ ...submission, consumed: consume });
7542
7583
  });
7543
7584
  // ── Tunnel Status ──────────────────────────────────────────────
7544
7585
  router.get('/tunnel', (_req, res) => {