instar 1.3.1007 → 1.3.1008

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.
@@ -3920,6 +3920,40 @@ export class AgentServer {
3920
3920
  const lastResultPath = options.config.stateDir
3921
3921
  ? path.join(options.config.stateDir, 'mentor-last-result.json')
3922
3922
  : null;
3923
+ const sweepMentorOrphans = () => {
3924
+ const outstanding = self.getOrCreateMentorOutstanding();
3925
+ const orphans = outstanding.sweepExpired();
3926
+ for (const orphan of orphans) {
3927
+ if (!outstanding.recordOrphanNotified(orphan.corr))
3928
+ continue;
3929
+ console.warn(`[mentor] orphaned prompt — no reply within ${orphan.ageMs}ms (corr=${orphan.corr}, mentee=${orphan.mentee})`);
3930
+ try {
3931
+ DegradationReporter.getInstance().report({
3932
+ feature: 'mentor.reply-orphaned',
3933
+ primary: 'mentor receives a correlated mentee reply within replyTimeoutMs',
3934
+ fallback: 'the content-key retry brake accounts for the attempt before any later send',
3935
+ reason: `outstanding prompt corr=${orphan.corr} aged ${orphan.ageMs}ms without a mentor-reply`,
3936
+ impact: 'the attempt is classified as unconfirmed delivery; identical content remains subject to its bounded retry budget',
3937
+ });
3938
+ }
3939
+ catch { /* best-effort */ }
3940
+ }
3941
+ };
3942
+ const escalateMentorDeliveryExhaustion = (contentKey, attempts, reason) => {
3943
+ const outstanding = self.getOrCreateMentorOutstanding();
3944
+ if (!outstanding.recordRetryExhaustionEscalated(contentKey))
3945
+ return;
3946
+ try {
3947
+ DegradationReporter.getInstance().report({
3948
+ feature: 'mentor.delivery-unconfirmed-retry-exhausted',
3949
+ primary: 'a mentor prompt produces a correlated mentee reply',
3950
+ fallback: 'the durable content-key breaker suppresses further identical sends',
3951
+ reason,
3952
+ impact: `that agenda item is paused as a transport/delivery failure after ${attempts} attempts; a genuinely new agenda item remains eligible`,
3953
+ });
3954
+ }
3955
+ catch { /* best-effort */ }
3956
+ };
3923
3957
  return new MentorOnboardingRunner({
3924
3958
  capture: (input) => ledger.captureRun(input),
3925
3959
  loadLastResult: lastResultPath
@@ -4102,6 +4136,11 @@ export class AgentServer {
4102
4136
  isMenteeBusy: () => {
4103
4137
  const cfg = getConfig();
4104
4138
  const menteeAgent = cfg.menteeAgentName || `instar-${cfg.menteeFramework}`;
4139
+ // Sweep + classify expired correlations BEFORE asking the busy gate.
4140
+ // canSendTo also sweeps defensively, but doing it here preserves the
4141
+ // orphan evidence instead of deleting it before the distinct delivery
4142
+ // signal can be emitted.
4143
+ sweepMentorOrphans();
4105
4144
  return !self.getOrCreateMentorOutstanding().canSendTo(menteeAgent).ok;
4106
4145
  },
4107
4146
  minIntervalElapsed: () => {
@@ -4145,30 +4184,24 @@ export class AgentServer {
4145
4184
  // instar-codey — using the derived name silently broke peer lookup.
4146
4185
  const menteeAgent = cfg.menteeAgentName || `instar-${framework}`;
4147
4186
  const corr = `mp-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
4148
- // Anti-ping-pong (spec §Fix 2b item 4 + Justin's original concern). Same
4149
- // logic regardless of transport. Refuse to send a new prompt while a
4150
- // prior one is unanswered within replyTimeoutMs.
4187
+ // Anti-ping-pong + content-key retry breaker. Reserve the attempt in
4188
+ // durable state BEFORE calling any transport. A failed reservation is
4189
+ // a hard refusal: acting without the restart-proof ledger would turn a
4190
+ // storage problem back into an unbounded self-action loop.
4151
4191
  const outstanding = self.getOrCreateMentorOutstanding();
4152
- const orphans = outstanding.sweepExpired();
4153
- for (const orphan of orphans) {
4154
- if (outstanding.recordOrphanNotified(orphan.corr)) {
4155
- console.warn(`[mentor] orphaned promptno reply within ${orphan.ageMs}ms (corr=${orphan.corr}, mentee=${orphan.mentee})`);
4156
- try {
4157
- DegradationReporter.getInstance().report({
4158
- feature: 'mentor.reply-orphaned',
4159
- primary: 'mentor receives Codey reply within replyTimeoutMs',
4160
- fallback: 'tick continues; no auto-resend; Stage-B sees the routed-sent row + no matching reply row',
4161
- reason: `outstanding prompt corr=${orphan.corr} aged ${orphan.ageMs}ms without a mentor-reply`,
4162
- impact: 'mentor cycle silently lost a reply; next tick allowed to retry',
4163
- });
4164
- }
4165
- catch { /* best-effort */ }
4192
+ const reservation = outstanding.reserveSend(corr, menteeAgent, message);
4193
+ if (!reservation.ok) {
4194
+ if (reservation.reason === 'prior-prompt-in-flight') {
4195
+ console.warn(`[mentor] deliverToMentee deferredprior-prompt-in-flight (corr=${reservation.outstandingCorr}, sentAt=${reservation.sentAt})`);
4196
+ return { delivered: false, reason: reservation.reason };
4166
4197
  }
4167
- }
4168
- const check = outstanding.canSendTo(menteeAgent);
4169
- if (!check.ok) {
4170
- console.warn(`[mentor] deliverToMentee deferred prior-prompt-in-flight (corr=${check.outstandingCorr}, sentAt=${check.sentAt})`);
4171
- return;
4198
+ if (reservation.reason === 'identical-content-retry-exhausted') {
4199
+ console.warn(`[mentor] deliverToMentee suppressed — identical-content-retry-exhausted (attempts=${reservation.attempts}, key=${reservation.contentKey.slice(0, 12)})`);
4200
+ escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempts, `${reservation.attempts} attempts for the same normalized mentor content ended without a confirmed reply`);
4201
+ return { delivered: false, reason: reservation.reason };
4202
+ }
4203
+ console.warn(`[mentor] deliverToMentee refused — ${reservation.reason}`);
4204
+ return { delivered: false, reason: reservation.reason };
4172
4205
  }
4173
4206
  // Deliver via the unified a2a transport: same-machine /a2a/inbox
4174
4207
  // when the mentee is a local peer (Telegram blocks bot-to-bot, so
@@ -4177,47 +4210,52 @@ export class AgentServer {
4177
4210
  const telegramBot = cfg.botToken && cfg.menteeChatId
4178
4211
  ? self.getOrCreateMentorBot(cfg.botToken, cfg.menteeChatId) ?? undefined
4179
4212
  : undefined;
4180
- const delivered = await self.deliverA2aMessage({
4181
- fromAgent: 'echo',
4182
- toAgent: menteeAgent,
4183
- role: 'mentor',
4184
- corr,
4185
- body: message,
4186
- allowedRoles: new Set(['mentor']),
4187
- // Route the mentor exchange to a DEDICATED mentor topic when one is
4188
- // configured, so the mentor's a2a check-ins don't interleave with
4189
- // the human↔mentee conversation topic (menteeTopicId). This one id
4190
- // drives both the /a2a/inbox body (where the mentee binds its
4191
- // session) and the Telegram fallback, so the whole exchange moves
4192
- // together. Falls back to menteeTopicId (backward-compatible).
4193
- telegramTopicId: resolveMentorDeliveryTopic(cfg),
4194
- targetMachineId: cfg.menteeMachineId,
4195
- // fromBotId = echo's mentor-bot id, so the mentee's allowlist
4196
- // (knownMentors[echo].botId === senderBotId) passes.
4197
- fromBotId: cfg.botToken ? cfg.botToken.split(':')[0] : undefined,
4198
- toBotId: cfg.menteeBotId,
4199
- telegramBot: telegramBot
4200
- ? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
4201
- : undefined,
4202
- botToken: cfg.botToken,
4203
- visibleEcho: {
4204
- enabled: cfg.visibleEcho !== false,
4205
- bot: telegramBot
4213
+ let delivered = false;
4214
+ try {
4215
+ delivered = await self.deliverA2aMessage({
4216
+ fromAgent: 'echo',
4217
+ toAgent: menteeAgent,
4218
+ role: 'mentor',
4219
+ corr,
4220
+ body: message,
4221
+ allowedRoles: new Set(['mentor']),
4222
+ // Route the mentor exchange to a DEDICATED mentor topic when one is
4223
+ // configured, so mentor a2a stays off the human conversation.
4224
+ telegramTopicId: resolveMentorDeliveryTopic(cfg),
4225
+ targetMachineId: cfg.menteeMachineId,
4226
+ fromBotId: cfg.botToken ? cfg.botToken.split(':')[0] : undefined,
4227
+ toBotId: cfg.menteeBotId,
4228
+ telegramBot: telegramBot
4206
4229
  ? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
4207
4230
  : undefined,
4208
- topicId: resolveMentorDeliveryTopic(cfg),
4209
- roleTag: '[mentor]',
4210
- reportFailure: (reason) => {
4211
- DegradationReporter.getInstance().report({
4212
- feature: 'mentor.visible-echo',
4213
- primary: 'successful inbox-local mentor delivery is mirrored visibly in Telegram',
4214
- fallback: 'canonical /a2a/inbox delivery remains successful; operator may see a phantom prompt',
4215
- reason,
4216
- impact: 'mentor exchange was delivered but its visible chat mirror is partial or absent',
4217
- });
4231
+ botToken: cfg.botToken,
4232
+ visibleEcho: {
4233
+ enabled: cfg.visibleEcho !== false,
4234
+ bot: telegramBot
4235
+ ? { sendToTopic: (t, txt) => telegramBot.sendToTopic(t, txt) }
4236
+ : undefined,
4237
+ topicId: resolveMentorDeliveryTopic(cfg),
4238
+ roleTag: '[mentor]',
4239
+ reportFailure: (reason) => {
4240
+ DegradationReporter.getInstance().report({
4241
+ feature: 'mentor.visible-echo',
4242
+ primary: 'successful inbox-local mentor delivery is mirrored visibly in Telegram',
4243
+ fallback: 'canonical /a2a/inbox delivery remains successful; operator may see a phantom prompt',
4244
+ reason,
4245
+ impact: 'mentor exchange was delivered but its visible chat mirror is partial or absent',
4246
+ });
4247
+ },
4218
4248
  },
4219
- },
4220
- });
4249
+ });
4250
+ }
4251
+ catch (err) {
4252
+ outstanding.markDeliveryFailed(corr);
4253
+ console.warn(`[mentor] deliverToMentee transport failed (corr=${corr}, mentee=${menteeAgent}):`, err instanceof Error ? err.message : String(err));
4254
+ if (reservation.exhausted) {
4255
+ escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempt, `${reservation.attempt} transport attempts for the same normalized mentor content threw before delivery confirmation`);
4256
+ }
4257
+ return { delivered: false, reason: 'transport-failed' };
4258
+ }
4221
4259
  if (delivered) {
4222
4260
  self.appendMentorSent(options.config.stateDir, {
4223
4261
  ts: Date.now(),
@@ -4227,10 +4265,18 @@ export class AgentServer {
4227
4265
  topicId: resolveMentorDeliveryTopic(cfg),
4228
4266
  message,
4229
4267
  });
4230
- outstanding.markSent(corr, menteeAgent);
4268
+ return { delivered: true };
4231
4269
  }
4232
4270
  else {
4271
+ outstanding.markDeliveryFailed(corr);
4233
4272
  console.warn(`[mentor] deliverToMentee did not deliver (corr=${corr}, mentee=${menteeAgent}) — no local peer + telegram fallback unavailable/blocked`);
4273
+ // The attempt remains in the content ledger. On the final allowed
4274
+ // attempt, emit the distinct delivery failure immediately instead
4275
+ // of waiting for another tick merely to discover the open breaker.
4276
+ if (reservation.exhausted) {
4277
+ escalateMentorDeliveryExhaustion(reservation.contentKey, reservation.attempt, `${reservation.attempt} transport attempts for the same normalized mentor content were refused`);
4278
+ }
4279
+ return { delivered: false, reason: 'transport-unavailable' };
4234
4280
  }
4235
4281
  },
4236
4282
  onTickRan: () => {