instar 0.28.20 → 0.28.21

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 (45) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/commands/init.js +2 -2
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +27 -22
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/core/CoherenceGate.d.ts +7 -0
  7. package/dist/core/CoherenceGate.d.ts.map +1 -1
  8. package/dist/core/CoherenceGate.js +57 -3
  9. package/dist/core/CoherenceGate.js.map +1 -1
  10. package/dist/core/CoherenceReviewer.d.ts +2 -0
  11. package/dist/core/CoherenceReviewer.d.ts.map +1 -1
  12. package/dist/core/CoherenceReviewer.js.map +1 -1
  13. package/dist/core/SendGateway.d.ts +13 -3
  14. package/dist/core/SendGateway.d.ts.map +1 -1
  15. package/dist/core/SendGateway.js +107 -4
  16. package/dist/core/SendGateway.js.map +1 -1
  17. package/dist/core/reviewers/claim-provenance.d.ts.map +1 -1
  18. package/dist/core/reviewers/claim-provenance.js +11 -1
  19. package/dist/core/reviewers/claim-provenance.js.map +1 -1
  20. package/dist/core/reviewers/url-validity.d.ts.map +1 -1
  21. package/dist/core/reviewers/url-validity.js +12 -1
  22. package/dist/core/reviewers/url-validity.js.map +1 -1
  23. package/dist/core/types.d.ts +1 -1
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/scaffold/templates.js +2 -2
  26. package/dist/server/AgentServer.d.ts +1 -0
  27. package/dist/server/AgentServer.d.ts.map +1 -1
  28. package/dist/server/routes.d.ts +4 -1
  29. package/dist/server/routes.d.ts.map +1 -1
  30. package/dist/server/routes.js +98 -22
  31. package/dist/server/routes.js.map +1 -1
  32. package/dist/threadline/ThreadlineMCPServer.d.ts +4 -0
  33. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
  34. package/dist/threadline/ThreadlineMCPServer.js +7 -1
  35. package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
  36. package/dist/threadline/ThreadlineRouter.d.ts +17 -1
  37. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  38. package/dist/threadline/ThreadlineRouter.js +64 -5
  39. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  40. package/dist/threadline/mcp-stdio-entry.js +2 -0
  41. package/dist/threadline/mcp-stdio-entry.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/data/builtin-manifest.json +48 -48
  44. package/upgrades/0.28.21.md +23 -0
  45. package/upgrades/NEXT.md +26 -3
@@ -3593,7 +3593,7 @@ export function createRoutes(ctx) {
3593
3593
  res.status(400).json({ error: '"context" must be a string under 5KB if provided' });
3594
3594
  return;
3595
3595
  }
3596
- const validTypes = ['bug', 'feature', 'improvement', 'question', 'other'];
3596
+ const validTypes = ['bug', 'feature', 'improvement', 'question', 'hallucination', 'other'];
3597
3597
  const feedbackType = validTypes.includes(type) ? type : 'other';
3598
3598
  // Semantic quality validation
3599
3599
  const quality = ctx.feedback.validateFeedbackQuality(title, description);
@@ -7265,7 +7265,10 @@ export function createRoutes(ctx) {
7265
7265
  // Check if this message resolves a pending waitForReply request.
7266
7266
  // Local delivery bypasses the relay client's gate-passed event, so we
7267
7267
  // must check reply waiters here directly.
7268
- if (senderAgent && ctx.threadlineReplyWaiters.size > 0) {
7268
+ // PR-3: resolve waiter by threadId (unique) rather than sender
7269
+ // agent name (which may collide across multiple same-named agents).
7270
+ const inboundThreadId = envelope.message?.threadId;
7271
+ if (inboundThreadId && ctx.threadlineReplyWaiters.size > 0) {
7269
7272
  let textContent;
7270
7273
  const body = envelope.message?.body;
7271
7274
  if (typeof body === 'string')
@@ -7275,21 +7278,35 @@ export function createRoutes(ctx) {
7275
7278
  }
7276
7279
  if (textContent) {
7277
7280
  const isAutoAck = textContent.startsWith('Message received.') || textContent.startsWith('Message received,');
7278
- const waiter = ctx.threadlineReplyWaiters.get(senderAgent);
7281
+ const waiter = ctx.threadlineReplyWaiters.get(inboundThreadId);
7279
7282
  if (waiter && !isAutoAck) {
7280
- console.log(`[relay-agent] Resolved reply waiter for ${senderAgent}`);
7283
+ console.log(`[relay-agent] Resolved reply waiter for thread ${inboundThreadId} (from ${senderAgent ?? 'unknown'})`);
7281
7284
  waiter.resolve(textContent);
7282
7285
  }
7283
7286
  }
7284
7287
  }
7285
- // If the message has a threadId and we have a ThreadlineRouter,
7286
- // route it through the threadline pipeline for session resume/spawn.
7287
- if (ctx.threadlineRouter && envelope.message?.threadId) {
7288
- // Fire-and-forget the relay is already accepted, threadline handling
7289
- // is best-effort for session management.
7290
- ctx.threadlineRouter.handleInboundMessage(envelope).catch(err => {
7288
+ // If we have a ThreadlineRouter, route the message through it for
7289
+ // session resume/spawn. We AWAIT the result so callers learn the real
7290
+ // outcome (spawned / resumed / injected / handled:false). The message
7291
+ // has already been accepted into the inbox, so we don't alter the HTTP
7292
+ // status unless the router throws.
7293
+ if (ctx.threadlineRouter) {
7294
+ try {
7295
+ const threadlineResult = await ctx.threadlineRouter.handleInboundMessage(envelope);
7296
+ res.json({ ok: true, threadline: threadlineResult });
7297
+ return;
7298
+ }
7299
+ catch (err) {
7291
7300
  console.error('[routes] ThreadlineRouter handling error:', err);
7292
- });
7301
+ res.json({
7302
+ ok: true,
7303
+ threadline: {
7304
+ handled: false,
7305
+ error: err instanceof Error ? err.message : 'Unknown error',
7306
+ },
7307
+ });
7308
+ return;
7309
+ }
7293
7310
  }
7294
7311
  res.json({ ok: true });
7295
7312
  }
@@ -7745,20 +7762,24 @@ export function createRoutes(ctx) {
7745
7762
  // ── Threadline Reply Waiter ─────────────────────────────────────────
7746
7763
  // Waits for an incoming reply from a specific agent on a specific thread.
7747
7764
  // Used by the relay-send endpoint when waitForReply is true.
7748
- function waitForThreadlineReply(routeCtx, senderAgent, _threadId, timeoutSec) {
7765
+ function waitForThreadlineReply(routeCtx, senderAgent, threadId, timeoutSec) {
7749
7766
  const timeout = Math.min(Math.max(timeoutSec ?? 120, 5), 300) * 1000; // 5s–300s, default 120s
7767
+ // PR-3: Waiters are keyed by threadId (unique per conversation) rather
7768
+ // than sender agent name (which can collide when multiple agents share
7769
+ // a name — e.g., two "luna" agents on different machines).
7750
7770
  return new Promise((resolve) => {
7751
7771
  const timer = setTimeout(() => {
7752
- routeCtx.threadlineReplyWaiters.delete(senderAgent);
7772
+ routeCtx.threadlineReplyWaiters.delete(threadId);
7753
7773
  resolve(null);
7754
7774
  }, timeout);
7755
- routeCtx.threadlineReplyWaiters.set(senderAgent, {
7775
+ routeCtx.threadlineReplyWaiters.set(threadId, {
7756
7776
  resolve: (reply) => {
7757
7777
  clearTimeout(timer);
7758
- routeCtx.threadlineReplyWaiters.delete(senderAgent);
7778
+ routeCtx.threadlineReplyWaiters.delete(threadId);
7759
7779
  resolve(reply);
7760
7780
  },
7761
- threadId: _threadId,
7781
+ threadId,
7782
+ senderAgent,
7762
7783
  timer,
7763
7784
  });
7764
7785
  });
@@ -7782,7 +7803,10 @@ export function createRoutes(ctx) {
7782
7803
  return;
7783
7804
  }
7784
7805
  const msgId = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
7785
- const effectiveThreadId = threadId ?? msgId;
7806
+ // Mint a stable UUID threadId when caller didn't provide one so
7807
+ // first-contact messages aren't dropped on the recipient side. Both
7808
+ // sender and recipient will agree on this id going forward.
7809
+ const effectiveThreadId = threadId ?? randomUUID();
7786
7810
  // ── Try local delivery first (same-machine agents) ──────────────
7787
7811
  // Read known-agents.json for local agent info. If the target is local,
7788
7812
  // deliver directly via their /messages/relay-agent endpoint, bypassing
@@ -7804,19 +7828,54 @@ export function createRoutes(ctx) {
7804
7828
  }
7805
7829
  }
7806
7830
  const nameMatches = agents.filter(a => a.name === targetName || a.name?.toLowerCase() === targetName?.toLowerCase());
7807
- // If multiple same-named agents, disambiguate by fingerprint prefix
7831
+ // PR-3: Fingerprint-based disambiguation. If multiple known agents
7832
+ // share a name, require a `name:fpPrefix` qualifier to pick one.
7833
+ // Previously this silently fell through to the relay, which then
7834
+ // also usually failed — masking the root cause.
7808
7835
  let localTarget = nameMatches.length === 1 ? nameMatches[0] : undefined;
7809
7836
  if (nameMatches.length > 1 && targetFpPrefix) {
7810
7837
  localTarget = nameMatches.find(a => {
7811
7838
  const fp = a.fingerprint || a.publicKey?.substring(0, 32);
7812
7839
  return fp?.toLowerCase().startsWith(targetFpPrefix);
7813
7840
  });
7841
+ if (!localTarget) {
7842
+ res.status(409).json({
7843
+ success: false,
7844
+ error: `No agent named "${targetName}" matches fingerprint prefix "${targetFpPrefix}". Known: ${nameMatches.map(a => `${a.name}:${(a.fingerprint || a.publicKey || '').substring(0, 8)}`).join(', ')}`,
7845
+ });
7846
+ return;
7847
+ }
7814
7848
  }
7815
7849
  else if (nameMatches.length > 1 && !targetFpPrefix) {
7816
- // Ambiguous skip local delivery, let relay handle it
7817
- localTarget = undefined;
7850
+ const hints = nameMatches.map(a => {
7851
+ const fp = (a.fingerprint || a.publicKey || '').substring(0, 8);
7852
+ return `"${a.name}:${fp}"`;
7853
+ }).join(', ');
7854
+ res.status(409).json({
7855
+ success: false,
7856
+ error: `Ambiguous target: ${nameMatches.length} known agents named "${targetName}". Use one of: ${hints}`,
7857
+ });
7858
+ return;
7859
+ }
7860
+ // PR-3: Self-guard by fingerprint when available, falling back to
7861
+ // name comparison. This prevents self-delivery when the agent's
7862
+ // name happens to match one of its own aliases in known-agents.json.
7863
+ let isSelfTarget = localTarget?.name === ctx.config.projectName;
7864
+ if (localTarget && !isSelfTarget) {
7865
+ try {
7866
+ const selfIdPath = path.join(ctx.config.stateDir, 'threadline', 'identity.json');
7867
+ if (fs.existsSync(selfIdPath)) {
7868
+ const selfId = JSON.parse(fs.readFileSync(selfIdPath, 'utf-8'));
7869
+ const selfFp = (selfId.fingerprint || '').toLowerCase();
7870
+ const targetFp = (localTarget.fingerprint || localTarget.publicKey?.substring(0, 32) || '').toLowerCase();
7871
+ if (selfFp && targetFp && selfFp === targetFp) {
7872
+ isSelfTarget = true;
7873
+ }
7874
+ }
7875
+ }
7876
+ catch { /* @silent-fallback-ok — identity.json read is best-effort */ }
7818
7877
  }
7819
- if (localTarget?.port && localTarget.name !== ctx.config.projectName) {
7878
+ if (localTarget?.port && !isSelfTarget) {
7820
7879
  // Check if the local agent is actually running
7821
7880
  try {
7822
7881
  const healthResp = await fetch(`http://localhost:${localTarget.port}/threadline/health`, {
@@ -7874,7 +7933,20 @@ export function createRoutes(ctx) {
7874
7933
  signal: AbortSignal.timeout(10000),
7875
7934
  });
7876
7935
  if (localResp.ok) {
7877
- console.log(`[relay-send] Local delivery to ${localTarget.name}:${localTarget.port} (thread: ${effectiveThreadId})`);
7936
+ let localRespBody = {};
7937
+ try {
7938
+ localRespBody = await localResp.json();
7939
+ }
7940
+ catch { /* no body */ }
7941
+ const tl = localRespBody.threadline;
7942
+ const outcome = tl?.injected ? 'injected into live session'
7943
+ : tl?.spawned ? 'spawned new session'
7944
+ : tl?.resumed ? 'resumed existing thread'
7945
+ : tl?.gateDecision === 'queue-for-approval' ? 'queued for approval'
7946
+ : tl?.error ? `error: ${tl.error}`
7947
+ : tl?.handled === false ? 'queued (no live session)'
7948
+ : 'accepted';
7949
+ console.log(`[relay-send] Local delivery to ${localTarget.name}:${localTarget.port} (thread: ${effectiveThreadId}) — ${outcome}`);
7878
7950
  if (waitForReply) {
7879
7951
  const reply = await waitForThreadlineReply(ctx, localTarget.name, effectiveThreadId, timeoutSeconds);
7880
7952
  res.json({
@@ -7883,6 +7955,8 @@ export function createRoutes(ctx) {
7883
7955
  threadId: effectiveThreadId,
7884
7956
  resolvedAgent: localTarget.name,
7885
7957
  deliveryPath: 'local',
7958
+ deliveryOutcome: outcome,
7959
+ threadline: tl,
7886
7960
  reply,
7887
7961
  });
7888
7962
  }
@@ -7893,6 +7967,8 @@ export function createRoutes(ctx) {
7893
7967
  threadId: effectiveThreadId,
7894
7968
  resolvedAgent: localTarget.name,
7895
7969
  deliveryPath: 'local',
7970
+ deliveryOutcome: outcome,
7971
+ threadline: tl,
7896
7972
  });
7897
7973
  }
7898
7974
  return;