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.
- package/dist/cli.js +0 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +27 -22
- package/dist/commands/server.js.map +1 -1
- package/dist/core/CoherenceGate.d.ts +7 -0
- package/dist/core/CoherenceGate.d.ts.map +1 -1
- package/dist/core/CoherenceGate.js +57 -3
- package/dist/core/CoherenceGate.js.map +1 -1
- package/dist/core/CoherenceReviewer.d.ts +2 -0
- package/dist/core/CoherenceReviewer.d.ts.map +1 -1
- package/dist/core/CoherenceReviewer.js.map +1 -1
- package/dist/core/SendGateway.d.ts +13 -3
- package/dist/core/SendGateway.d.ts.map +1 -1
- package/dist/core/SendGateway.js +107 -4
- package/dist/core/SendGateway.js.map +1 -1
- package/dist/core/reviewers/claim-provenance.d.ts.map +1 -1
- package/dist/core/reviewers/claim-provenance.js +11 -1
- package/dist/core/reviewers/claim-provenance.js.map +1 -1
- package/dist/core/reviewers/url-validity.d.ts.map +1 -1
- package/dist/core/reviewers/url-validity.js +12 -1
- package/dist/core/reviewers/url-validity.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -2
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/routes.d.ts +4 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +98 -22
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.d.ts +4 -0
- package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
- package/dist/threadline/ThreadlineMCPServer.js +7 -1
- package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
- package/dist/threadline/ThreadlineRouter.d.ts +17 -1
- package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
- package/dist/threadline/ThreadlineRouter.js +64 -5
- package/dist/threadline/ThreadlineRouter.js.map +1 -1
- package/dist/threadline/mcp-stdio-entry.js +2 -0
- package/dist/threadline/mcp-stdio-entry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/0.28.21.md +23 -0
- package/upgrades/NEXT.md +26 -3
package/dist/server/routes.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
7286
|
-
//
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
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,
|
|
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(
|
|
7772
|
+
routeCtx.threadlineReplyWaiters.delete(threadId);
|
|
7753
7773
|
resolve(null);
|
|
7754
7774
|
}, timeout);
|
|
7755
|
-
routeCtx.threadlineReplyWaiters.set(
|
|
7775
|
+
routeCtx.threadlineReplyWaiters.set(threadId, {
|
|
7756
7776
|
resolve: (reply) => {
|
|
7757
7777
|
clearTimeout(timer);
|
|
7758
|
-
routeCtx.threadlineReplyWaiters.delete(
|
|
7778
|
+
routeCtx.threadlineReplyWaiters.delete(threadId);
|
|
7759
7779
|
resolve(reply);
|
|
7760
7780
|
},
|
|
7761
|
-
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
7817
|
-
|
|
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 &&
|
|
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
|
-
|
|
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;
|