instar 1.3.53 → 1.3.55
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 +21 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/machine.d.ts.map +1 -1
- package/dist/commands/machine.js +25 -0
- package/dist/commands/machine.js.map +1 -1
- package/dist/commands/test-as-self.d.ts +41 -0
- package/dist/commands/test-as-self.d.ts.map +1 -0
- package/dist/commands/test-as-self.js +259 -0
- package/dist/commands/test-as-self.js.map +1 -0
- package/dist/commands/testAsSelfValidation.d.ts +43 -0
- package/dist/commands/testAsSelfValidation.d.ts.map +1 -0
- package/dist/commands/testAsSelfValidation.js +84 -0
- package/dist/commands/testAsSelfValidation.js.map +1 -0
- package/dist/core/GitSync.d.ts +7 -0
- package/dist/core/GitSync.d.ts.map +1 -1
- package/dist/core/GitSync.js +36 -0
- package/dist/core/GitSync.js.map +1 -1
- package/dist/core/mergeRegistry.d.ts +28 -0
- package/dist/core/mergeRegistry.d.ts.map +1 -0
- package/dist/core/mergeRegistry.js +86 -0
- package/dist/core/mergeRegistry.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +14 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +11 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +239 -211
- package/dist/server/AgentServer.js.map +1 -1
- package/package.json +1 -1
- package/scripts/post-publish-smoke.mjs +80 -0
- package/src/data/builtin-manifest.json +32 -32
- package/src/scaffold/templates.ts +14 -0
- package/upgrades/1.3.53.md +20 -28
- package/upgrades/1.3.54.md +43 -0
- package/upgrades/1.3.55.md +38 -0
- package/upgrades/side-effects/init-join-launchagent-handoff.md +62 -0
- package/upgrades/side-effects/mentee-reply-leg.md +61 -0
- package/upgrades/side-effects/mesh-git-substrate-auto-reconcile.md +70 -0
- package/upgrades/side-effects/publish-completeness-smoke-gate.md +47 -0
- package/upgrades/side-effects/test-as-self-orchestrator.md +66 -0
|
@@ -753,96 +753,230 @@ export class AgentServer {
|
|
|
753
753
|
const adapter = this.telegramAdapter;
|
|
754
754
|
if (!adapter)
|
|
755
755
|
return;
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
756
|
+
const self = this;
|
|
757
|
+
const ledger = this.getOrCreateA2aLedger();
|
|
758
|
+
const localAgent = this.config.projectName;
|
|
759
|
+
// The primary adapter's hook carries ALL a2a roles this agent accepts —
|
|
760
|
+
// both as-mentee (`mentor`) and as-mentor (`mentor-reply`). Same-machine
|
|
761
|
+
// a2a (Telegram blocks bot-to-bot) routes through /a2a/inbox → this hook.
|
|
762
|
+
const knownAgents = {};
|
|
763
|
+
const acceptRoles = {};
|
|
764
|
+
const roleHandlers = new Map();
|
|
765
|
+
// ── Mentee side: accept `mentor` from configured known-mentors ──
|
|
766
|
+
const menteeCfg = this.getMenteeConfigSnapshot();
|
|
767
|
+
const menteeReady = menteeCfg.enabled &&
|
|
768
|
+
!!menteeCfg.localAgentName &&
|
|
769
|
+
Object.keys(menteeCfg.knownMentors).length > 0 &&
|
|
770
|
+
!!menteeCfg.replyChatId &&
|
|
771
|
+
!!menteeCfg.replyTopicId;
|
|
772
|
+
if (menteeCfg.enabled && !menteeReady) {
|
|
773
|
+
console.warn('[mentee] receiver wiring skipped — mentee.enabled but missing localAgentName / knownMentors / replyChatId / replyTopicId');
|
|
762
774
|
}
|
|
763
|
-
if (
|
|
764
|
-
|
|
765
|
-
|
|
775
|
+
if (menteeReady) {
|
|
776
|
+
for (const [mentorName, info] of Object.entries(menteeCfg.knownMentors)) {
|
|
777
|
+
knownAgents[mentorName] = info;
|
|
778
|
+
acceptRoles[mentorName] = [...(acceptRoles[mentorName] ?? []), 'mentor'];
|
|
779
|
+
}
|
|
780
|
+
const sessionTimeoutMs = menteeCfg.sessionTimeoutMs;
|
|
781
|
+
const mentorMessageHandler = async (msg, _ctx) => {
|
|
782
|
+
// Spawn a mentee session, bounded-wait, capture the reply. Capture the
|
|
783
|
+
// last non-empty pane snapshot WHILE the session is alive — the post-
|
|
784
|
+
// completion capture races the reaper (the session's tmux pane is gone
|
|
785
|
+
// by the time we detect completion, so captureOutput returns empty).
|
|
786
|
+
let reply = '';
|
|
787
|
+
let sessionId = '';
|
|
788
|
+
try {
|
|
789
|
+
const session = await self.sessionManager.spawnSession({
|
|
790
|
+
name: `mentee-handle-${Date.now()}`,
|
|
791
|
+
prompt: msg.body,
|
|
792
|
+
maxDurationMinutes: Math.max(1, Math.ceil(sessionTimeoutMs / 60_000)),
|
|
793
|
+
});
|
|
794
|
+
sessionId = session.id;
|
|
795
|
+
const tmux = session.tmuxSession;
|
|
796
|
+
const pollIntervalMs = 2_000;
|
|
797
|
+
const pollIterations = Math.max(1, Math.ceil(sessionTimeoutMs / pollIntervalMs));
|
|
798
|
+
let finished = false;
|
|
799
|
+
for (let i = 0; i < pollIterations; i++) {
|
|
800
|
+
const stillRunning = self.sessionManager
|
|
801
|
+
.listRunningSessions()
|
|
802
|
+
.some((s) => s.id === session.id);
|
|
803
|
+
// Capture-while-alive: keep the last non-empty snapshot so a
|
|
804
|
+
// completed-then-reaped session still yields its output.
|
|
805
|
+
const snapshot = self.sessionManager.captureOutput(tmux, 200) ?? '';
|
|
806
|
+
if (snapshot.trim())
|
|
807
|
+
reply = snapshot;
|
|
808
|
+
if (!stillRunning) {
|
|
809
|
+
finished = true;
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
813
|
+
}
|
|
814
|
+
if (!finished) {
|
|
815
|
+
try {
|
|
816
|
+
self.sessionManager.killSession(session.id);
|
|
817
|
+
}
|
|
818
|
+
catch { /* best-effort */ }
|
|
819
|
+
console.warn(`[mentee] session ${session.id} timed out at ${sessionTimeoutMs}ms (corr=${msg.corr}); sending best-effort partial reply`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
catch (err) {
|
|
823
|
+
console.warn(`[mentee] mentor-message handler spawn failed (corr=${msg.corr}, sessionId=${sessionId}):`, err instanceof Error ? err.message : String(err));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (!reply.trim()) {
|
|
827
|
+
console.warn(`[mentee] mentee session produced empty reply for corr=${msg.corr}; skipping reply-out`);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
// Reply OUT via the a2a transport (same-machine /a2a/inbox when the
|
|
831
|
+
// mentor is a local peer; Telegram fallback otherwise). The handler
|
|
832
|
+
// stays capture-only — deliverA2aMessage is an orchestrator method,
|
|
833
|
+
// not a capability handed into the handler.
|
|
834
|
+
await self.deliverA2aMessage({
|
|
835
|
+
fromAgent: localAgent,
|
|
836
|
+
toAgent: msg.from,
|
|
837
|
+
role: 'mentor-reply',
|
|
838
|
+
corr: msg.corr || msg.id,
|
|
839
|
+
body: reply,
|
|
840
|
+
allowedRoles: new Set(['mentor-reply']),
|
|
841
|
+
telegramTopicId: menteeCfg.replyTopicId,
|
|
842
|
+
toBotId: menteeCfg.knownMentors[msg.from]?.botId,
|
|
843
|
+
});
|
|
844
|
+
};
|
|
845
|
+
roleHandlers.set('mentor', mentorMessageHandler);
|
|
766
846
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
847
|
+
// ── Mentor side: accept `mentor-reply` from the configured mentee ──
|
|
848
|
+
// So a mentee's reply arriving via /a2a/inbox reaches the same
|
|
849
|
+
// finding-emission-only handler that installMentorReceiverHook uses on
|
|
850
|
+
// the mentor-BOT adapter (spec §250 — capability-handle, capture-only).
|
|
851
|
+
const mentorCfg = this.getMentorConfigSnapshot();
|
|
852
|
+
if (mentorCfg.menteeBotId) {
|
|
853
|
+
const menteeAgent = `instar-${mentorCfg.menteeFramework}`;
|
|
854
|
+
knownAgents[menteeAgent] = { botId: mentorCfg.menteeBotId };
|
|
855
|
+
acceptRoles[menteeAgent] = [...(acceptRoles[menteeAgent] ?? []), 'mentor-reply'];
|
|
856
|
+
const outstanding = this.getOrCreateMentorOutstanding();
|
|
857
|
+
const replyJsonl = path.join(this.config.stateDir, 'mentor-replies.jsonl');
|
|
858
|
+
this.mentorReplyJsonlPath = replyJsonl;
|
|
859
|
+
const mentorReplyHandler = async (msg, ctx) => {
|
|
860
|
+
const had = outstanding.clearByCorr(msg.corr);
|
|
861
|
+
if (!had) {
|
|
862
|
+
console.warn(`[mentor] mentor-reply for unknown corr=${msg.corr} (late after orphan-sweep?); persisting anyway`);
|
|
863
|
+
}
|
|
864
|
+
try {
|
|
865
|
+
fs.mkdirSync(path.dirname(replyJsonl), { recursive: true });
|
|
866
|
+
const row = {
|
|
867
|
+
ts: Date.now(),
|
|
868
|
+
fromAgent: msg.from,
|
|
869
|
+
corr: msg.corr,
|
|
870
|
+
replyId: msg.id,
|
|
871
|
+
topicId: ctx.topicId,
|
|
872
|
+
message: msg.body,
|
|
873
|
+
transport: 'a2a-inbox-local',
|
|
874
|
+
};
|
|
875
|
+
fs.appendFileSync(replyJsonl, JSON.stringify(row) + '\n', 'utf-8');
|
|
876
|
+
console.log(`[mentor] mentor-reply persisted (corr=${msg.corr}, from=${msg.from}) → mentor-replies.jsonl`);
|
|
877
|
+
}
|
|
878
|
+
catch (err) {
|
|
879
|
+
console.warn('[mentor] mentor-reply persist failed (non-fatal):', err instanceof Error ? err.message : String(err));
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
roleHandlers.set('mentor-reply', mentorReplyHandler);
|
|
770
883
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
884
|
+
if (roleHandlers.size === 0) {
|
|
885
|
+
// Neither side configured — nothing to install. /a2a/inbox will return
|
|
886
|
+
// agentMessage:false until a role-handler exists.
|
|
887
|
+
return;
|
|
774
888
|
}
|
|
775
889
|
const recipientCfg = {
|
|
776
|
-
localAgent
|
|
777
|
-
knownAgents
|
|
890
|
+
localAgent,
|
|
891
|
+
knownAgents,
|
|
778
892
|
acceptRoles,
|
|
779
893
|
skewWindowMs: 24 * 60 * 60 * 1000,
|
|
780
894
|
maxVersion: A2A_VERSION,
|
|
781
895
|
};
|
|
782
|
-
const
|
|
896
|
+
const hook = buildAgentMessageHook({
|
|
897
|
+
config: recipientCfg,
|
|
898
|
+
ledger,
|
|
899
|
+
processedIds: this.getOrCreateA2aProcessedIds(),
|
|
900
|
+
roleHandlers,
|
|
901
|
+
});
|
|
902
|
+
adapter.setAgentMessageHook(hook);
|
|
903
|
+
console.log(`[a2a] primary-adapter hook installed — localAgent=${localAgent}, roles=[${[...roleHandlers.keys()].join(',')}], knownAgents=[${Object.keys(knownAgents).join(',')}]`);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Deliver an a2a message to another agent. Same-machine peers (registered in
|
|
907
|
+
* AgentRegistry) receive it via HTTP POST to their `/a2a/inbox` endpoint —
|
|
908
|
+
* the canonical transport because Telegram structurally blocks bot-to-bot
|
|
909
|
+
* delivery. Cross-machine peers fall back to the Telegram bot path (preserved
|
|
910
|
+
* but currently unreachable due to the same block; tracked follow-up).
|
|
911
|
+
*
|
|
912
|
+
* Used by BOTH directions: the mentor's `deliverToMentee` (role=mentor) and
|
|
913
|
+
* the mentee's reply (role=mentor-reply). Returns true iff delivered.
|
|
914
|
+
*/
|
|
915
|
+
async deliverA2aMessage(opts) {
|
|
783
916
|
const ledger = this.getOrCreateA2aLedger();
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
917
|
+
const id = `a2a-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
918
|
+
// ── Same-machine: POST to peer's /a2a/inbox ──
|
|
919
|
+
try {
|
|
920
|
+
const { listAgents } = await import('../core/AgentRegistry.js');
|
|
921
|
+
const { getAgentToken } = await import('../messaging/AgentTokenManager.js');
|
|
922
|
+
const peers = listAgents();
|
|
923
|
+
const localPeer = peers.find((a) => a.name === opts.toAgent && a.name !== this.config.projectName);
|
|
924
|
+
if (localPeer?.port) {
|
|
925
|
+
const peerToken = getAgentToken(localPeer.name);
|
|
926
|
+
if (peerToken) {
|
|
927
|
+
const tsNow = Date.now();
|
|
928
|
+
const marker = `[a2a:from=${opts.fromAgent} to=${opts.toAgent} role=${opts.role} id=${id} corr=${opts.corr} ts=${tsNow} v=${A2A_VERSION}]`;
|
|
929
|
+
const fullText = `${marker}\n\n${opts.body}`;
|
|
930
|
+
const resp = await fetch(`http://localhost:${localPeer.port}/a2a/inbox`, {
|
|
931
|
+
method: 'POST',
|
|
932
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${peerToken}` },
|
|
933
|
+
body: JSON.stringify({
|
|
934
|
+
text: fullText,
|
|
935
|
+
topicId: opts.telegramTopicId ?? 0,
|
|
936
|
+
senderAgent: opts.fromAgent,
|
|
937
|
+
senderIsBot: true,
|
|
938
|
+
senderBotId: opts.toBotId ?? `${opts.fromAgent}-local`,
|
|
939
|
+
}),
|
|
940
|
+
signal: AbortSignal.timeout(10_000),
|
|
941
|
+
});
|
|
942
|
+
if (resp.ok) {
|
|
943
|
+
const result = (await resp.json().catch(() => ({})));
|
|
944
|
+
if (result.agentMessage === true) {
|
|
945
|
+
try {
|
|
946
|
+
ledger.appendSent({
|
|
947
|
+
ts: tsNow, from: opts.fromAgent, to: opts.toAgent, role: opts.role,
|
|
948
|
+
id, corr: opts.corr, result: 'sent', transport: 'a2a-inbox-local',
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
catch { /* best-effort */ }
|
|
952
|
+
console.log(`[a2a] delivered → ${opts.toAgent} via local /a2a/inbox (role=${opts.role}, corr=${opts.corr})`);
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
console.warn(`[a2a] local /a2a/inbox refused (to=${opts.toAgent}, role=${opts.role}, reason=${result.reason ?? 'unknown'})`);
|
|
809
956
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
if (!finished) {
|
|
813
|
-
try {
|
|
814
|
-
self.sessionManager.killSession(session.id);
|
|
957
|
+
else {
|
|
958
|
+
console.warn(`[a2a] local /a2a/inbox HTTP ${resp.status} (to=${opts.toAgent}, role=${opts.role})`);
|
|
815
959
|
}
|
|
816
|
-
catch { /* best-effort */ }
|
|
817
|
-
console.warn(`[mentee] session ${session.id} timed out at ${sessionTimeoutMs}ms (corr=${msg.corr}); skipping reply-out`);
|
|
818
|
-
return;
|
|
819
960
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
console.warn(`[mentee] mentor-message handler spawn failed (corr=${msg.corr}, sessionId=${sessionId}):`, err instanceof Error ? err.message : String(err));
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
if (!reply.trim()) {
|
|
827
|
-
console.warn(`[mentee] mentee session produced empty reply for corr=${msg.corr}; skipping reply-out`);
|
|
828
|
-
return;
|
|
961
|
+
else {
|
|
962
|
+
console.warn(`[a2a] no token for local peer ${localPeer.name}; cannot deliver ${opts.role}`);
|
|
963
|
+
}
|
|
829
964
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
965
|
+
}
|
|
966
|
+
catch (err) {
|
|
967
|
+
console.warn(`[a2a] local-inbox delivery attempt failed (to=${opts.toAgent}): ${err instanceof Error ? err.message : String(err)}`);
|
|
968
|
+
}
|
|
969
|
+
// ── Cross-machine Telegram fallback (mentor→mentee only; currently
|
|
970
|
+
// unreachable due to bot-to-bot block — tracked follow-up). ──
|
|
971
|
+
if (opts.telegramBot && opts.telegramTopicId !== undefined && opts.toBotId && opts.botToken) {
|
|
833
972
|
try {
|
|
834
973
|
const result = await sendAgentMessage({
|
|
835
|
-
fromAgent:
|
|
836
|
-
|
|
837
|
-
role: 'mentor-reply',
|
|
838
|
-
toTopicId: cfg.replyTopicId,
|
|
839
|
-
message: reply,
|
|
840
|
-
id: `mr-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
841
|
-
correlationId: msg.corr || msg.id,
|
|
974
|
+
fromAgent: opts.fromAgent, toAgent: opts.toAgent, role: opts.role,
|
|
975
|
+
toTopicId: opts.telegramTopicId, message: opts.body, id, correlationId: opts.corr,
|
|
842
976
|
}, {
|
|
843
977
|
send: async (topicId, text) => {
|
|
844
978
|
try {
|
|
845
|
-
const res = await
|
|
979
|
+
const res = await opts.telegramBot.sendToTopic(topicId, text);
|
|
846
980
|
return { ok: true, messageId: String(res.messageId) };
|
|
847
981
|
}
|
|
848
982
|
catch (e) {
|
|
@@ -851,32 +985,19 @@ export class AgentServer {
|
|
|
851
985
|
},
|
|
852
986
|
appendAudit: (row) => ledger.appendSent(row),
|
|
853
987
|
now: () => Date.now(),
|
|
854
|
-
mintId: () =>
|
|
855
|
-
allowedRoles:
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
// for the botToken-scrub helper.
|
|
860
|
-
botToken: undefined,
|
|
861
|
-
fromBotId: '',
|
|
862
|
-
toBotId: senderBotId,
|
|
988
|
+
mintId: () => id,
|
|
989
|
+
allowedRoles: opts.allowedRoles,
|
|
990
|
+
botToken: opts.botToken,
|
|
991
|
+
fromBotId: opts.botToken.split(':')[0],
|
|
992
|
+
toBotId: opts.toBotId,
|
|
863
993
|
});
|
|
864
|
-
|
|
865
|
-
console.warn(`[mentee] mentor-reply send refused (corr=${msg.corr}): ${result.reason ?? 'unknown'}`);
|
|
866
|
-
}
|
|
994
|
+
return result.ok;
|
|
867
995
|
}
|
|
868
996
|
catch (err) {
|
|
869
|
-
console.warn(`[
|
|
997
|
+
console.warn(`[a2a] telegram fallback failed (to=${opts.toAgent}): ${err instanceof Error ? err.message : String(err)}`);
|
|
870
998
|
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
config: recipientCfg,
|
|
874
|
-
ledger,
|
|
875
|
-
processedIds: this.getOrCreateA2aProcessedIds(),
|
|
876
|
-
roleHandlers: new Map([['mentor', mentorMessageHandler]]),
|
|
877
|
-
});
|
|
878
|
-
adapter.setAgentMessageHook(hook);
|
|
879
|
-
console.log(`[mentee] receiver wiring installed — localAgent=${cfg.localAgentName}, knownMentors=[${Object.keys(cfg.knownMentors).join(',')}], reply→${cfg.replyChatId}/topic ${cfg.replyTopicId}`);
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
880
1001
|
}
|
|
881
1002
|
/** Snapshot of mentee config for the receiver-wiring installer. */
|
|
882
1003
|
getMenteeConfigSnapshot() {
|
|
@@ -1031,125 +1152,32 @@ export class AgentServer {
|
|
|
1031
1152
|
console.warn(`[mentor] deliverToMentee deferred — prior-prompt-in-flight (corr=${check.outstandingCorr}, sentAt=${check.sentAt})`);
|
|
1032
1153
|
return;
|
|
1033
1154
|
}
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
1036
|
-
// is
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
headers: {
|
|
1058
|
-
'Content-Type': 'application/json',
|
|
1059
|
-
'Authorization': `Bearer ${peerToken}`,
|
|
1060
|
-
},
|
|
1061
|
-
body: JSON.stringify({
|
|
1062
|
-
text: fullText,
|
|
1063
|
-
topicId: cfg.menteeTopicId ?? 0,
|
|
1064
|
-
senderAgent: 'echo',
|
|
1065
|
-
senderIsBot: true,
|
|
1066
|
-
senderBotId: cfg.botToken ? cfg.botToken.split(':')[0] : 'echo-local',
|
|
1067
|
-
}),
|
|
1068
|
-
signal: AbortSignal.timeout(10_000),
|
|
1069
|
-
});
|
|
1070
|
-
if (resp.ok) {
|
|
1071
|
-
const result = (await resp.json().catch(() => ({})));
|
|
1072
|
-
if (result.agentMessage === true) {
|
|
1073
|
-
outstanding.markSent(corr, menteeAgent);
|
|
1074
|
-
try {
|
|
1075
|
-
const ledger = self.getOrCreateA2aLedger();
|
|
1076
|
-
ledger.appendSent({
|
|
1077
|
-
ts: tsNow,
|
|
1078
|
-
from: 'echo',
|
|
1079
|
-
to: menteeAgent,
|
|
1080
|
-
role: 'mentor',
|
|
1081
|
-
id: corr,
|
|
1082
|
-
corr,
|
|
1083
|
-
result: 'sent',
|
|
1084
|
-
transport: 'a2a-inbox-local',
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
catch { /* best-effort */ }
|
|
1088
|
-
console.log(`[mentor] deliverToMentee → local a2a inbox (peer=${menteeAgent}:${localPeer.port}, corr=${corr})`);
|
|
1089
|
-
deliveredLocally = true;
|
|
1090
|
-
}
|
|
1091
|
-
else {
|
|
1092
|
-
console.warn(`[mentor] deliverToMentee local-inbox refused (reason=${result.reason ?? 'unknown'}, peer=${menteeAgent}); falling back to Telegram bot transport`);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
else {
|
|
1096
|
-
console.warn(`[mentor] deliverToMentee local-inbox HTTP ${resp.status} (peer=${menteeAgent}); falling back to Telegram bot transport`);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
else {
|
|
1100
|
-
console.warn(`[mentor] deliverToMentee local-inbox skipped — no token for peer ${localPeer.name}; falling back to Telegram bot transport`);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
catch (err) {
|
|
1105
|
-
console.warn(`[mentor] deliverToMentee local-inbox attempt failed (non-fatal, falling back to Telegram): ${err instanceof Error ? err.message : String(err)}`);
|
|
1106
|
-
}
|
|
1107
|
-
if (deliveredLocally)
|
|
1108
|
-
return;
|
|
1109
|
-
// Telegram bot fallback — preserved for cross-machine peers (not
|
|
1110
|
-
// currently deliverable due to Telegram bot-to-bot block; tracked).
|
|
1111
|
-
if (!cfg.botToken || !cfg.menteeBotId || !cfg.menteeChatId || cfg.menteeTopicId === undefined) {
|
|
1112
|
-
console.warn(`[mentor] deliverToMentee skipped — mentor-bot config incomplete (need mentor.botToken + menteeBotId + menteeChatId + menteeTopicId; framework=${framework})`);
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
const bot = self.getOrCreateMentorBot(cfg.botToken, cfg.menteeChatId);
|
|
1116
|
-
if (!bot)
|
|
1117
|
-
return;
|
|
1118
|
-
const ledger = self.getOrCreateA2aLedger();
|
|
1119
|
-
try {
|
|
1120
|
-
const result = await sendAgentMessage({
|
|
1121
|
-
fromAgent: 'echo',
|
|
1122
|
-
toAgent: menteeAgent,
|
|
1123
|
-
role: 'mentor',
|
|
1124
|
-
toTopicId: cfg.menteeTopicId,
|
|
1125
|
-
message,
|
|
1126
|
-
id: corr,
|
|
1127
|
-
correlationId: corr,
|
|
1128
|
-
}, {
|
|
1129
|
-
send: async (topicId, text) => {
|
|
1130
|
-
try {
|
|
1131
|
-
const id = await bot.sendToTopic(topicId, text);
|
|
1132
|
-
return { ok: true, messageId: String(id ?? '') };
|
|
1133
|
-
}
|
|
1134
|
-
catch (err) {
|
|
1135
|
-
return { ok: false, error: err };
|
|
1136
|
-
}
|
|
1137
|
-
},
|
|
1138
|
-
appendAudit: (row) => ledger.appendSent(row),
|
|
1139
|
-
now: () => Date.now(),
|
|
1140
|
-
mintId: () => corr,
|
|
1141
|
-
allowedRoles: new Set(['mentor']),
|
|
1142
|
-
botToken: cfg.botToken,
|
|
1143
|
-
fromBotId: cfg.botToken.split(':')[0],
|
|
1144
|
-
toBotId: cfg.menteeBotId,
|
|
1145
|
-
});
|
|
1146
|
-
if (result.ok) {
|
|
1147
|
-
// Mark only on successful send — a failed send doesn't owe a reply.
|
|
1148
|
-
outstanding.markSent(corr, menteeAgent);
|
|
1149
|
-
}
|
|
1155
|
+
// Deliver via the unified a2a transport: same-machine /a2a/inbox
|
|
1156
|
+
// when the mentee is a local peer (Telegram blocks bot-to-bot, so
|
|
1157
|
+
// this is the canonical path), else the Telegram bot fallback
|
|
1158
|
+
// (cross-machine; currently unreachable, tracked follow-up).
|
|
1159
|
+
const telegramBot = cfg.botToken && cfg.menteeChatId
|
|
1160
|
+
? self.getOrCreateMentorBot(cfg.botToken, cfg.menteeChatId) ?? undefined
|
|
1161
|
+
: undefined;
|
|
1162
|
+
const delivered = await self.deliverA2aMessage({
|
|
1163
|
+
fromAgent: 'echo',
|
|
1164
|
+
toAgent: menteeAgent,
|
|
1165
|
+
role: 'mentor',
|
|
1166
|
+
corr,
|
|
1167
|
+
body: message,
|
|
1168
|
+
allowedRoles: new Set(['mentor']),
|
|
1169
|
+
telegramTopicId: cfg.menteeTopicId,
|
|
1170
|
+
toBotId: cfg.menteeBotId,
|
|
1171
|
+
telegramBot: telegramBot
|
|
1172
|
+
? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
|
|
1173
|
+
: undefined,
|
|
1174
|
+
botToken: cfg.botToken,
|
|
1175
|
+
});
|
|
1176
|
+
if (delivered) {
|
|
1177
|
+
outstanding.markSent(corr, menteeAgent);
|
|
1150
1178
|
}
|
|
1151
|
-
|
|
1152
|
-
console.warn(
|
|
1179
|
+
else {
|
|
1180
|
+
console.warn(`[mentor] deliverToMentee did not deliver (corr=${corr}, mentee=${menteeAgent}) — no local peer + telegram fallback unavailable/blocked`);
|
|
1153
1181
|
}
|
|
1154
1182
|
},
|
|
1155
1183
|
onTickRan: () => {
|