pi-advisor-flow 0.8.2-dev.1 → 0.8.3-dev.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.
package/dist/index.js CHANGED
@@ -1454,10 +1454,12 @@ import net from "node:net";
1454
1454
  var REDACTION_MARKER = "[REDACTED SECRET]";
1455
1455
  var PEM_BEGIN_PATTERN = /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/giu;
1456
1456
  var PEM_END_PATTERN = /-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/iu;
1457
+ var CREDENTIAL_NAME = String.raw`(?:api[_-]?key|token|secret|password|passwd)`;
1458
+ var SECRET_ASSIGNMENT_PATTERN = new RegExp(String.raw`(?:(?:"(?:[a-z0-9]+[_-])*?${CREDENTIAL_NAME}"|'(?:[a-z0-9]+[_-])*?${CREDENTIAL_NAME}')|(?:\b|_)${CREDENTIAL_NAME})\s*[:=]\s*(?:"(?:\\.|[^"])*(?:"|$)|'(?:\\.|[^'])*(?:'|$)|[^\s"'&,;)}\]]+)`, "giu");
1457
1459
  var SECRET_PATTERNS = [
1458
1460
  /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/giu,
1459
1461
  /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/giu,
1460
- /\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s"'&,;)}\]]+)/giu,
1462
+ SECRET_ASSIGNMENT_PATTERN,
1461
1463
  /(?<scheme>[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/giu,
1462
1464
  /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
1463
1465
  /\b(?:aws_secret_access_key|aws_session_token)\s*[:=]\s*[^\s"'&,;)}\]]+/giu
@@ -1495,9 +1497,6 @@ var nextSequence = () => {
1495
1497
  return sequence;
1496
1498
  };
1497
1499
  var emitBlocked;
1498
- var setHerdrBlockedEmitter = (emitter) => {
1499
- emitBlocked = emitter;
1500
- };
1501
1500
  var safeEmitBlocked = (active, label = "Advisor blocked") => {
1502
1501
  try {
1503
1502
  emitBlocked?.(active, label);
@@ -1538,9 +1537,11 @@ var createHerdrNotificationRequest = (title, body) => ({
1538
1537
  // src/herdr-block.ts
1539
1538
  class HerdrAdvisorBlock {
1540
1539
  #blocked = false;
1540
+ emitBlocked;
1541
1541
  report;
1542
1542
  enabled;
1543
- constructor(report = sendToHerdr, enabled = () => true) {
1543
+ constructor(report = sendToHerdr, enabled = () => true, emitBlocked = safeEmitBlocked) {
1544
+ this.emitBlocked = emitBlocked;
1544
1545
  this.report = report;
1545
1546
  this.enabled = enabled;
1546
1547
  }
@@ -1551,7 +1552,7 @@ class HerdrAdvisorBlock {
1551
1552
  const label = cleanNotification(reason, 200);
1552
1553
  const wasBlocked = this.#blocked;
1553
1554
  if (!wasBlocked) {
1554
- safeEmitBlocked(true, label);
1555
+ this.safeEmitBlocked(true, label);
1555
1556
  }
1556
1557
  this.#blocked = true;
1557
1558
  this.safeReport({ blocked: label });
@@ -1562,7 +1563,7 @@ class HerdrAdvisorBlock {
1562
1563
  if (!wasBlocked) {
1563
1564
  return;
1564
1565
  }
1565
- safeEmitBlocked(false);
1566
+ this.safeEmitBlocked(false, "Advisor blocked");
1566
1567
  try {
1567
1568
  this.report({
1568
1569
  id: `${BLOCK_SOURCE}:${nextSequence()}`,
@@ -1578,6 +1579,14 @@ class HerdrAdvisorBlock {
1578
1579
  });
1579
1580
  } catch {}
1580
1581
  }
1582
+ safeEmitBlocked(active, label) {
1583
+ try {
1584
+ this.emitBlocked(active, label);
1585
+ return true;
1586
+ } catch {
1587
+ return false;
1588
+ }
1589
+ }
1581
1590
  safeReport(labels) {
1582
1591
  try {
1583
1592
  this.report({
@@ -1612,6 +1621,8 @@ var metadataRequest = (clear) => ({
1612
1621
 
1613
1622
  class HerdrAdvisorActivity {
1614
1623
  #activeConsultations = 0;
1624
+ #activeByOwner = new Map;
1625
+ #defaultOwner = Symbol("default Herdr Advisor activity");
1615
1626
  report;
1616
1627
  enabled;
1617
1628
  constructor(report = sendToHerdr, enabled = () => true) {
@@ -1619,29 +1630,67 @@ class HerdrAdvisorActivity {
1619
1630
  this.enabled = enabled;
1620
1631
  }
1621
1632
  start() {
1633
+ return this.startFor(this.#defaultOwner);
1634
+ }
1635
+ finish() {
1636
+ this.finishFor(this.#defaultOwner);
1637
+ }
1638
+ clear() {
1639
+ this.clearFor(this.#defaultOwner);
1640
+ }
1641
+ createScope() {
1642
+ const owner = Symbol("Herdr Advisor runtime");
1643
+ return {
1644
+ clear: () => this.clearFor(owner),
1645
+ start: () => this.startFor(owner)
1646
+ };
1647
+ }
1648
+ startFor(owner) {
1649
+ const lease = Symbol("Herdr activity lease");
1622
1650
  if (!this.enabled()) {
1623
- return;
1651
+ return () => this.finishLease(owner, lease);
1652
+ }
1653
+ let leases = this.#activeByOwner.get(owner);
1654
+ if (!leases) {
1655
+ leases = new Set;
1656
+ this.#activeByOwner.set(owner, leases);
1624
1657
  }
1658
+ leases.add(lease);
1625
1659
  this.#activeConsultations += 1;
1626
1660
  if (this.#activeConsultations === 1) {
1627
1661
  this.safeReport(false);
1628
1662
  }
1663
+ return () => this.finishLease(owner, lease);
1629
1664
  }
1630
- finish() {
1631
- if (this.#activeConsultations === 0) {
1665
+ finishFor(owner) {
1666
+ const lease = this.#activeByOwner.get(owner)?.values().next().value;
1667
+ if (lease) {
1668
+ this.finishLease(owner, lease);
1669
+ }
1670
+ }
1671
+ finishLease(owner, lease) {
1672
+ const leases = this.#activeByOwner.get(owner);
1673
+ if (!leases?.delete(lease)) {
1632
1674
  return;
1633
1675
  }
1676
+ if (leases.size === 0) {
1677
+ this.#activeByOwner.delete(owner);
1678
+ }
1634
1679
  this.#activeConsultations -= 1;
1635
1680
  if (this.#activeConsultations === 0) {
1636
1681
  this.safeReport(true);
1637
1682
  }
1638
1683
  }
1639
- clear() {
1640
- if (this.#activeConsultations === 0) {
1684
+ clearFor(owner) {
1685
+ const leases = this.#activeByOwner.get(owner);
1686
+ if (!leases?.size) {
1641
1687
  return;
1642
1688
  }
1643
- this.#activeConsultations = 0;
1644
- this.safeReport(true);
1689
+ this.#activeByOwner.delete(owner);
1690
+ this.#activeConsultations -= leases.size;
1691
+ if (this.#activeConsultations === 0) {
1692
+ this.safeReport(true);
1693
+ }
1645
1694
  }
1646
1695
  safeReport(clear) {
1647
1696
  try {
@@ -3859,6 +3908,7 @@ class AdvisorSessionState {
3859
3908
  #ledger = freshAdviceLedger();
3860
3909
  #usage = freshUsage();
3861
3910
  #consumedCalls = 0;
3911
+ #callReservations = new Set;
3862
3912
  #jev = new AdvisorJevLedgerState;
3863
3913
  #sessionTurnOrdinal = 0;
3864
3914
  #turnsSinceConsultation = 0;
@@ -3868,6 +3918,7 @@ class AdvisorSessionState {
3868
3918
  this.#ledger = freshAdviceLedger();
3869
3919
  this.#usage = freshUsage();
3870
3920
  this.#consumedCalls = 0;
3921
+ this.#callReservations.clear();
3871
3922
  this.#jev.reset();
3872
3923
  this.#sessionTurnOrdinal = 0;
3873
3924
  this.#turnsSinceConsultation = 0;
@@ -3901,14 +3952,34 @@ class AdvisorSessionState {
3901
3952
  this.#repetition.interventions += 1;
3902
3953
  return true;
3903
3954
  }
3904
- canConsult(limit) {
3905
- return limit === undefined || this.#consumedCalls < limit;
3955
+ canConsult(limit, reservationId) {
3956
+ const reservations = this.#callReservations.size - Number(reservationId !== undefined && this.#callReservations.has(reservationId));
3957
+ return limit === undefined || this.#consumedCalls + reservations < limit;
3958
+ }
3959
+ reserveCall(id, limit) {
3960
+ if (this.#callReservations.has(id)) {
3961
+ return true;
3962
+ }
3963
+ if (!this.canConsult(limit)) {
3964
+ return false;
3965
+ }
3966
+ this.#callReservations.add(id);
3967
+ return true;
3968
+ }
3969
+ releaseCall(id) {
3970
+ this.#callReservations.delete(id);
3906
3971
  }
3907
- consumeCall() {
3972
+ clearCallReservations() {
3973
+ this.#callReservations.clear();
3974
+ }
3975
+ consumeCall(reservationId) {
3976
+ if (reservationId !== undefined) {
3977
+ this.#callReservations.delete(reservationId);
3978
+ }
3908
3979
  this.#consumedCalls += 1;
3909
3980
  }
3910
3981
  remainingCalls(limit) {
3911
- return limit === undefined ? undefined : Math.max(0, limit - this.#consumedCalls);
3982
+ return limit === undefined ? undefined : Math.max(0, limit - this.#consumedCalls - this.#callReservations.size);
3912
3983
  }
3913
3984
  get consumedCalls() {
3914
3985
  return this.#consumedCalls;
@@ -4104,6 +4175,7 @@ var requestManualRender = (ctx) => {
4104
4175
 
4105
4176
  class CommandRuntime {
4106
4177
  advisorSessionState;
4178
+ herdrActivity;
4107
4179
  manualConsultations = new Map;
4108
4180
  manualProgress = new Map;
4109
4181
  manualProgressTimers = new Map;
@@ -4118,6 +4190,7 @@ class CommandRuntime {
4118
4190
  constructor(pi, dependencies = {}) {
4119
4191
  this.pi = pi;
4120
4192
  this.advisorSessionState = dependencies.sessionState ?? advisorSessionState;
4193
+ this.herdrActivity = dependencies.herdrActivity ?? herdrAdvisorActivity;
4121
4194
  this.scoutStatus = dependencies.statusManager ?? new ScoutStatusManager(false);
4122
4195
  this.requestAdvisor = dependencies.consult ?? ((ctx, question, signal, onChunk, onScout, gitContext) => consultAdvisor(ctx, question, signal, onChunk, "manual", gitContext, undefined, undefined, undefined, onScout));
4123
4196
  }
@@ -4325,7 +4398,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
4325
4398
  runtime.manualConsultations.clear();
4326
4399
  runtime.manualProgressTimers.clear();
4327
4400
  runtime.manualProgress.clear();
4328
- herdrAdvisorActivity.clear();
4401
+ runtime.herdrActivity.clear();
4329
4402
  });
4330
4403
  };
4331
4404
 
@@ -4676,14 +4749,19 @@ var connectionFailure = (error) => {
4676
4749
  failure: new JevFailureError("network", `Jev connection failed: ${message}`)
4677
4750
  };
4678
4751
  };
4679
- var sleepWithAbort = (signal, ms) => new Promise((resolve) => {
4680
- const timer = setTimeout(resolve, ms);
4681
- timer.unref?.();
4682
- signal.addEventListener("abort", () => {
4683
- clearTimeout(timer);
4684
- resolve();
4685
- }, { once: true });
4686
- });
4752
+ var sleepWithAbort = (signal, ms) => {
4753
+ if (signal.aborted) {
4754
+ return Promise.resolve();
4755
+ }
4756
+ return new Promise((resolve) => {
4757
+ const timer = setTimeout(resolve, ms);
4758
+ timer.unref?.();
4759
+ signal.addEventListener("abort", () => {
4760
+ clearTimeout(timer);
4761
+ resolve();
4762
+ }, { once: true });
4763
+ });
4764
+ };
4687
4765
 
4688
4766
  class JevClient {
4689
4767
  #apiKey;
@@ -4729,7 +4807,7 @@ class JevClient {
4729
4807
  let outcome = {};
4730
4808
  for (let attempt = 1;; attempt += 1) {
4731
4809
  outcome = await this.#attempt(body, deadline.signal);
4732
- if (!outcome.retryable || attempt >= MAX_ATTEMPTS) {
4810
+ if (!outcome.retryable || attempt >= MAX_ATTEMPTS || deadline.signal.aborted) {
4733
4811
  break;
4734
4812
  }
4735
4813
  await sleepWithAbort(deadline.signal, RETRY_BACKOFF_MS);
@@ -4759,12 +4837,12 @@ class JevClient {
4759
4837
  if (deadlineHit && outcome.failure?.category !== "auth") {
4760
4838
  throw new JevFailureError("timeout", `Jev call exceeded its ${this.#timeoutMs} ms wall-time budget.`);
4761
4839
  }
4762
- if (outcome.failure) {
4763
- throw outcome.failure;
4764
- }
4765
4840
  if (signal?.aborted) {
4766
4841
  throw new Error("Jev call aborted by the caller.");
4767
4842
  }
4843
+ if (outcome.failure) {
4844
+ throw outcome.failure;
4845
+ }
4768
4846
  throw new JevFailureError("error", "Jev call failed.");
4769
4847
  }
4770
4848
  async#attempt(body, signal) {
@@ -5227,11 +5305,12 @@ var screenWithJev = async (ctx, session, options, deps, normalizedQuestion) => {
5227
5305
  session.recordJevFilterAllowed();
5228
5306
  return allow();
5229
5307
  } catch (error) {
5308
+ if (options.signal?.aborted) {
5309
+ throw error;
5310
+ }
5230
5311
  session.recordJevFilterFailure();
5231
5312
  if (error instanceof JevFailureError) {
5232
5313
  notifyOutageOnce(ctx, error.category, error.message);
5233
- } else if (options.signal?.aborted) {
5234
- throw error;
5235
5314
  } else {
5236
5315
  notifyOutageOnce(ctx, "error", error instanceof Error ? error.message : String(error));
5237
5316
  }
@@ -5266,7 +5345,6 @@ var screeningSkipText = (outcome) => outcome.kind === "repeat" && outcome.reatta
5266
5345
 
5267
5346
  // src/commands/manual-consultation.ts
5268
5347
  var startManualConsultation = async (runtime, ctx, question, controller, scoutStatusToken, progress, gitContext) => {
5269
- herdrAdvisorActivity.start();
5270
5348
  progress.phase = "preparing";
5271
5349
  runtime.requestManualRender(ctx);
5272
5350
  if (ctx.hasUI) {
@@ -5279,6 +5357,7 @@ var startManualConsultation = async (runtime, ctx, question, controller, scoutSt
5279
5357
  }, 80);
5280
5358
  runtime.manualProgressTimers.set(controller, timer);
5281
5359
  }
5360
+ const finishHerdrActivity = runtime.herdrActivity.start();
5282
5361
  let scoutDetails;
5283
5362
  try {
5284
5363
  const { adviceId, markdown, usage } = await runtime.requestAdvisor(ctx, question, controller.signal, (thinking, text) => {
@@ -5362,6 +5441,7 @@ ${markdown}`,
5362
5441
  notify(ctx, `Advisor consultation failed: ${message}`, "error");
5363
5442
  notifyHerdrAdvisorFailure("Advisor consultation failed", message);
5364
5443
  } finally {
5444
+ finishHerdrActivity();
5365
5445
  if (controller.signal.aborted) {
5366
5446
  progress.phase = "cancelled";
5367
5447
  }
@@ -5373,7 +5453,6 @@ ${markdown}`,
5373
5453
  runtime.requestManualRender(ctx);
5374
5454
  runtime.scoutStatus.release(ctx, scoutStatusToken);
5375
5455
  runtime.manualConsultations.delete(controller);
5376
- herdrAdvisorActivity.finish();
5377
5456
  }
5378
5457
  };
5379
5458
 
@@ -5886,7 +5965,7 @@ class JevSetupSubmenu {
5886
5965
  }
5887
5966
  actions() {
5888
5967
  if (!this.credentials) {
5889
- return ["enter-key", "done"];
5968
+ return this.options.currentValue === "On" ? ["enter-key", "disable", "done"] : ["enter-key", "done"];
5890
5969
  }
5891
5970
  const enabled = this.options.currentValue === "On";
5892
5971
  const actions = [enabled ? "verify-again" : "verify-enable"];
@@ -5942,8 +6021,12 @@ class JevSetupSubmenu {
5942
6021
  return;
5943
6022
  }
5944
6023
  const result = await clear();
5945
- this.credentials = undefined;
5946
6024
  this.notice = result.message;
6025
+ if (!result.ok) {
6026
+ this.options.tui.requestRender();
6027
+ return;
6028
+ }
6029
+ this.credentials = undefined;
5947
6030
  this.options.done("Off");
5948
6031
  }
5949
6032
  async verifyAndEnable() {
@@ -6970,7 +7053,7 @@ var gateDecisionEffect = (decision, failureMode) => {
6970
7053
  }
6971
7054
  return decision === "blocked" ? gateFailureEffectForMode(failureMode) : "tool-blocked";
6972
7055
  };
6973
- var failureEffect = (category, message, ctx, session, failureMode) => {
7056
+ var failureEffect = (category, message, ctx, session, failureMode, herdrBlock = herdrAdvisorBlock) => {
6974
7057
  const reason = `Advisor gate ${category}: ${message}`;
6975
7058
  notifyLocalFailure(ctx, message, failureMode === "block-session");
6976
7059
  notifyHerdrAdvisorFailure("Advisor gate failure", reason);
@@ -6981,13 +7064,13 @@ var failureEffect = (category, message, ctx, session, failureMode) => {
6981
7064
  return { block: true, effect: "tool-blocked", reason };
6982
7065
  }
6983
7066
  session.block(reason);
6984
- herdrAdvisorBlock.set(reason);
7067
+ herdrBlock.set(reason);
6985
7068
  if (advisorBlockOnBlockedRef) {
6986
7069
  ctx.abort();
6987
7070
  }
6988
7071
  return { block: true, effect: "session-blocked", reason };
6989
7072
  };
6990
- var blockedDecisionEffect = (reason, ctx, session, failureMode) => {
7073
+ var blockedDecisionEffect = (reason, ctx, session, failureMode, herdrBlock = herdrAdvisorBlock) => {
6991
7074
  if (failureMode === "warn-and-continue") {
6992
7075
  if (ctx.hasUI) {
6993
7076
  ctx.ui.notify("Advisor gate returned blocked; continuing by configuration.", "warning");
@@ -6998,7 +7081,7 @@ var blockedDecisionEffect = (reason, ctx, session, failureMode) => {
6998
7081
  return { block: true, effect: "tool-blocked", reason };
6999
7082
  }
7000
7083
  session.block(reason);
7001
- herdrAdvisorBlock.set(reason);
7084
+ herdrBlock.set(reason);
7002
7085
  if (advisorBlockOnBlockedRef) {
7003
7086
  ctx.abort();
7004
7087
  }
@@ -7008,7 +7091,7 @@ var reserveAdvisorCall = (event, ctx, session, reservedCalls) => {
7008
7091
  if (event.toolName !== "ask_advisor" || isSimpleMode()) {
7009
7092
  return;
7010
7093
  }
7011
- if (!session.canConsult(getAdvisorMaxCallsPerSession())) {
7094
+ if (!session.reserveCall(event.toolCallId, getAdvisorMaxCallsPerSession())) {
7012
7095
  const message = "Advisor call budget exhausted for this session.";
7013
7096
  if (ctx.hasUI) {
7014
7097
  ctx.ui.notify(message, "warning");
@@ -7031,15 +7114,22 @@ var turnGateQuestion = {
7031
7114
  };
7032
7115
  var outageNotifier2 = createOutageNotifier((category, message) => `Advisor Jev turn gate failed (${category}); continuing without a proactive consultation. ${message}`);
7033
7116
  var notifyFailureOnce = outageNotifier2.notify;
7117
+ var shouldRunTurnGate = (registration, ctx, session) => {
7118
+ const interval = advisorJevTurnGateEveryTurnsRef;
7119
+ return interval > 0 && session.turnsSinceConsultation > 0 && session.turnsSinceConsultation % interval === 0 && !isSimpleMode() && !session.blocked && registration.activeTools().includes("ask_advisor") && advisorModelIsAllowed(ctx) && session.canConsult(getAdvisorMaxCallsPerSession());
7120
+ };
7121
+ var consumeTurnGateBudget = (session, shouldConsult) => {
7122
+ if (!shouldConsult || !session.canConsult(getAdvisorMaxCallsPerSession())) {
7123
+ return false;
7124
+ }
7125
+ session.consumeCall();
7126
+ return true;
7127
+ };
7034
7128
  var resetJevTurnGateNotification = outageNotifier2.reset;
7035
7129
  var handleJevTurnEnd = async (registration, ctx) => {
7036
7130
  const { session } = registration;
7037
7131
  session.recordCompletedTurn();
7038
- const interval = advisorJevTurnGateEveryTurnsRef;
7039
- if (interval <= 0 || session.turnsSinceConsultation <= 0 || session.turnsSinceConsultation % interval !== 0) {
7040
- return;
7041
- }
7042
- if (isSimpleMode() || session.blocked || !registration.activeTools().includes("ask_advisor") || !advisorModelIsAllowed(ctx) || !session.canConsult(getAdvisorMaxCallsPerSession())) {
7132
+ if (!shouldRunTurnGate(registration, ctx, session)) {
7043
7133
  return;
7044
7134
  }
7045
7135
  const consult = registration.deps?.consult ?? registration.consult;
@@ -7057,21 +7147,21 @@ var handleJevTurnEnd = async (registration, ctx) => {
7057
7147
  });
7058
7148
  session.recordJevGateCheck(result.usage);
7059
7149
  const shouldConsult = composeTurnGateVerdict(result.answers, advisorJevTurnGateNoulThresholdRef);
7060
- if (!shouldConsult) {
7150
+ if (!consumeTurnGateBudget(session, shouldConsult)) {
7061
7151
  return;
7062
7152
  }
7063
- session.consumeCall();
7064
- herdrAdvisorActivity.start();
7065
- registration.send({
7066
- content: "Proactive Advisor turn review",
7067
- customType: "advisor-turn-gate-call",
7068
- details: {
7069
- question: `Turn gate: ${session.turnsSinceConsultation} turns without a consultation`,
7070
- turn: session.sessionTurnOrdinal
7071
- },
7072
- display: true
7073
- });
7153
+ const herdrActivity = registration.herdrActivity ?? herdrAdvisorActivity;
7154
+ const finishHerdrActivity = herdrActivity.start();
7074
7155
  try {
7156
+ registration.send({
7157
+ content: "Proactive Advisor turn review",
7158
+ customType: "advisor-turn-gate-call",
7159
+ details: {
7160
+ question: `Turn gate: ${session.turnsSinceConsultation} turns without a consultation`,
7161
+ turn: session.sessionTurnOrdinal
7162
+ },
7163
+ display: true
7164
+ });
7075
7165
  const consulted = await consult(ctx, undefined, ctx.signal, undefined, "turn-gate");
7076
7166
  session.recordJevGateConsultation();
7077
7167
  session.resetTurnsSinceConsultation();
@@ -7095,7 +7185,7 @@ var handleJevTurnEnd = async (registration, ctx) => {
7095
7185
  display: true
7096
7186
  });
7097
7187
  } finally {
7098
- herdrAdvisorActivity.finish();
7188
+ finishHerdrActivity();
7099
7189
  }
7100
7190
  } catch (error) {
7101
7191
  session.recordJevGateFailure();
@@ -7254,6 +7344,7 @@ var claimTrackedHandoff = (session, includeTrackedFiles) => {
7254
7344
  };
7255
7345
  var registerAskAdvisorTool = ({
7256
7346
  consult: requestAdvisor,
7347
+ herdrActivity,
7257
7348
  pi,
7258
7349
  reservedCalls,
7259
7350
  screen,
@@ -7262,128 +7353,159 @@ var registerAskAdvisorTool = ({
7262
7353
  pi.registerTool({
7263
7354
  description: "Consult the on-demand Advisor model for strategic guidance. Call with an empty object for a contextual review; attach an optional draft for concrete plan or completion review. If the Advisor explicitly names a missing file, you may make a sequential follow-up call with includeTrackedFiles when enabled and relevant.",
7264
7355
  async execute(_id, params, signal, onUpdate, ctx) {
7265
- reservedCalls.delete(_id);
7266
- assertAdvisorModelAccess(ctx);
7267
- if (!(isSimpleMode() || session.canConsult(getAdvisorMaxCallsPerSession()))) {
7268
- throw new Error("Advisor call budget exhausted for this session.");
7356
+ try {
7357
+ assertAdvisorModelAccess(ctx);
7358
+ } catch (error) {
7359
+ session.releaseCall(_id);
7360
+ reservedCalls.delete(_id);
7361
+ throw error;
7269
7362
  }
7270
- const normalizedQuestion = normalizeScreeningQuestion(resolveAdvisorRequest(params.question));
7271
- const screening = await screen(ctx, session, {
7272
- draft: params.draft,
7273
- force: params.force,
7274
- question: resolveAdvisorRequest(params.question),
7275
- signal
7276
- });
7277
- if (screening.decision === "skip") {
7278
- const skipText = screeningSkipText(screening);
7279
- return {
7280
- content: [{ text: skipText, type: "text" }],
7281
- details: {
7282
- jev: {
7283
- kind: screening.kind,
7284
- reason: screening.reason,
7285
- skipped: true
7286
- },
7287
- text: skipText
7288
- }
7289
- };
7363
+ const simpleMode = isSimpleMode();
7364
+ if (!simpleMode && !session.reserveCall(_id, getAdvisorMaxCallsPerSession())) {
7365
+ throw new Error("Advisor call budget exhausted for this session.");
7290
7366
  }
7291
- claimTrackedHandoff(session, params.includeTrackedFiles);
7292
- if (!isSimpleMode()) {
7293
- session.consumeCall();
7294
- session.resetTurnsSinceConsultation();
7367
+ if (simpleMode) {
7368
+ session.releaseCall(_id);
7369
+ reservedCalls.delete(_id);
7370
+ } else {
7371
+ reservedCalls.add(_id);
7295
7372
  }
7296
- herdrAdvisorActivity.start();
7297
- let scoutDetails;
7298
- const coalescedUpdate = createCoalescedUpdate((update) => onUpdate?.(update), ADVISOR_STREAM_UPDATE_INTERVAL_MS);
7299
- const flushUpdate = () => {
7300
- const result = coalescedUpdate.flush();
7301
- if (result.failed) {
7302
- throw result.error;
7303
- }
7304
- };
7373
+ let normalizedQuestion;
7305
7374
  try {
7306
- const result = await requestAdvisor(ctx, resolveAdvisorRequest(params.question), signal, (t, tx) => coalescedUpdate.update({
7307
- content: [{ text: tx, type: "text" }],
7308
- details: {
7309
- advisor: advisorRef,
7310
- question: resolveAdvisorRequest(params.question),
7311
- scout: scoutDetails,
7312
- text: tx,
7313
- thinking: t
7375
+ if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
7376
+ throw new Error("Advisor call budget exhausted for this session.");
7377
+ }
7378
+ normalizedQuestion = normalizeScreeningQuestion(resolveAdvisorRequest(params.question));
7379
+ const screening = await screen(ctx, session, {
7380
+ draft: params.draft,
7381
+ force: params.force,
7382
+ question: resolveAdvisorRequest(params.question),
7383
+ signal
7384
+ });
7385
+ if (screening.decision === "skip") {
7386
+ const skipText = screeningSkipText(screening);
7387
+ session.releaseCall(_id);
7388
+ reservedCalls.delete(_id);
7389
+ return {
7390
+ content: [{ text: skipText, type: "text" }],
7391
+ details: {
7392
+ jev: {
7393
+ kind: screening.kind,
7394
+ reason: screening.reason,
7395
+ skipped: true
7396
+ },
7397
+ text: skipText
7398
+ }
7399
+ };
7400
+ }
7401
+ if (!simpleMode && !session.canConsult(getAdvisorMaxCallsPerSession(), _id)) {
7402
+ throw new Error("Advisor call budget exhausted for this session.");
7403
+ }
7404
+ claimTrackedHandoff(session, params.includeTrackedFiles);
7405
+ if (!simpleMode) {
7406
+ session.consumeCall(_id);
7407
+ reservedCalls.delete(_id);
7408
+ session.resetTurnsSinceConsultation();
7409
+ }
7410
+ } catch (error) {
7411
+ session.releaseCall(_id);
7412
+ reservedCalls.delete(_id);
7413
+ throw error;
7414
+ }
7415
+ const runConsultation = async () => {
7416
+ let scoutDetails;
7417
+ const coalescedUpdate = createCoalescedUpdate((update) => onUpdate?.(update), ADVISOR_STREAM_UPDATE_INTERVAL_MS);
7418
+ const flushUpdate = () => {
7419
+ const result = coalescedUpdate.flush();
7420
+ if (result.failed) {
7421
+ throw result.error;
7314
7422
  }
7315
- }), "executor-requested", params.gitContext === "none" ? "off" : params.gitContext, params.draft, params.includeUntracked, params.includeTrackedFiles, (event) => {
7316
- scoutDetails = scoutDetailsFromEvent(event, scoutDetails);
7317
- coalescedUpdate.update({
7318
- content: [{ text: scoutDetails.text ?? "", type: "text" }],
7423
+ };
7424
+ const finishHerdrActivity = herdrActivity.start();
7425
+ try {
7426
+ const result = await requestAdvisor(ctx, resolveAdvisorRequest(params.question), signal, (t, tx) => coalescedUpdate.update({
7427
+ content: [{ text: tx, type: "text" }],
7319
7428
  details: {
7320
7429
  advisor: advisorRef,
7321
7430
  question: resolveAdvisorRequest(params.question),
7322
- scout: scoutDetails
7431
+ scout: scoutDetails,
7432
+ text: tx,
7433
+ thinking: t
7323
7434
  }
7435
+ }), "executor-requested", params.gitContext === "none" ? "off" : params.gitContext, params.draft, params.includeUntracked, params.includeTrackedFiles, (event) => {
7436
+ scoutDetails = scoutDetailsFromEvent(event, scoutDetails);
7437
+ coalescedUpdate.update({
7438
+ content: [{ text: scoutDetails.text ?? "", type: "text" }],
7439
+ details: {
7440
+ advisor: advisorRef,
7441
+ question: resolveAdvisorRequest(params.question),
7442
+ scout: scoutDetails
7443
+ }
7444
+ });
7445
+ }, _id);
7446
+ flushUpdate();
7447
+ session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes), normalizedQuestion);
7448
+ session.recordInvocation({
7449
+ cost: advisorUsageCost(result.usage),
7450
+ executionEffect: "continued",
7451
+ kind: "markdown",
7452
+ model: result.model,
7453
+ trigger: "executor-requested",
7454
+ usage: result.usage
7324
7455
  });
7325
- }, _id);
7326
- flushUpdate();
7327
- session.issueAdvice(result.adviceId, result.markdown, result.trigger, Boolean(result.draftBytes), normalizedQuestion);
7328
- session.recordInvocation({
7329
- cost: advisorUsageCost(result.usage),
7330
- executionEffect: "continued",
7331
- kind: "markdown",
7332
- model: result.model,
7333
- trigger: "executor-requested",
7334
- usage: result.usage
7335
- });
7336
- const usage = snapshotAdvisorUsage(result.usage);
7337
- const piUsage = advisorUsageForPi(result.usage);
7338
- updateAdvisorUsageStatus(ctx, session);
7339
- const details = {
7340
- adviceId: result.adviceId,
7341
- advisor: result.model,
7342
- draftBytes: result.draftBytes,
7343
- preferenceBytes: result.preferenceBytes,
7344
- question: resolveAdvisorRequest(params.question),
7345
- scout: scoutDetails,
7346
- text: result.markdown,
7347
- thinking: result.thinkingText,
7348
- trackedBytes: result.trackedBytes,
7349
- untrackedBytes: result.untrackedBytes
7350
- };
7351
- if (usage) {
7352
- details.usage = usage;
7353
- }
7354
- const response = {
7355
- content: [
7356
- {
7357
- text: `Advisor (${result.model})
7456
+ const usage = snapshotAdvisorUsage(result.usage);
7457
+ const piUsage = advisorUsageForPi(result.usage);
7458
+ updateAdvisorUsageStatus(ctx, session);
7459
+ const details = {
7460
+ adviceId: result.adviceId,
7461
+ advisor: result.model,
7462
+ draftBytes: result.draftBytes,
7463
+ preferenceBytes: result.preferenceBytes,
7464
+ question: resolveAdvisorRequest(params.question),
7465
+ scout: scoutDetails,
7466
+ text: result.markdown,
7467
+ thinking: result.thinkingText,
7468
+ trackedBytes: result.trackedBytes,
7469
+ untrackedBytes: result.untrackedBytes
7470
+ };
7471
+ if (usage) {
7472
+ details.usage = usage;
7473
+ }
7474
+ const response = {
7475
+ content: [
7476
+ {
7477
+ text: `Advisor (${result.model})
7358
7478
 
7359
7479
  ${result.markdown}`,
7360
- type: "text"
7361
- }
7362
- ],
7363
- details
7364
- };
7365
- if (piUsage) {
7366
- response.usage = piUsage;
7480
+ type: "text"
7481
+ }
7482
+ ],
7483
+ details
7484
+ };
7485
+ if (piUsage) {
7486
+ response.usage = piUsage;
7487
+ }
7488
+ return response;
7489
+ } catch (error) {
7490
+ coalescedUpdate.flush();
7491
+ const message = error instanceof Error ? error.message : String(error);
7492
+ session.recordInvocation({
7493
+ executionEffect: "continued",
7494
+ failure: "provider-error",
7495
+ kind: "markdown",
7496
+ model: advisorRef,
7497
+ trigger: "executor-requested"
7498
+ });
7499
+ updateAdvisorUsageStatus(ctx, session);
7500
+ notifyLocalFailure(ctx, message);
7501
+ notifyHerdrAdvisorFailure("Advisor consultation failed", message);
7502
+ throw error;
7503
+ } finally {
7504
+ finishHerdrActivity();
7505
+ coalescedUpdate.cancel();
7367
7506
  }
7368
- return response;
7369
- } catch (error) {
7370
- coalescedUpdate.flush();
7371
- const message = error instanceof Error ? error.message : String(error);
7372
- session.recordInvocation({
7373
- executionEffect: "continued",
7374
- failure: "provider-error",
7375
- kind: "markdown",
7376
- model: advisorRef,
7377
- trigger: "executor-requested"
7378
- });
7379
- updateAdvisorUsageStatus(ctx, session);
7380
- notifyLocalFailure(ctx, message);
7381
- notifyHerdrAdvisorFailure("Advisor consultation failed", message);
7382
- throw error;
7383
- } finally {
7384
- coalescedUpdate.cancel();
7385
- herdrAdvisorActivity.finish();
7386
- }
7507
+ };
7508
+ return runConsultation();
7387
7509
  },
7388
7510
  label: "Ask Advisor",
7389
7511
  name: "ask_advisor",
@@ -7461,7 +7583,7 @@ var sendAutomaticGateResult = (pi, result) => {
7461
7583
  display: true
7462
7584
  }, { deliverAs: "steer" });
7463
7585
  };
7464
- var applyGateDecision = (pi, ctx, session, result, reason, failureMode) => {
7586
+ var applyGateDecision = (pi, ctx, session, result, reason, failureMode, herdrBlock) => {
7465
7587
  if (!result.ok) {
7466
7588
  session.recordInvocation({
7467
7589
  executionEffect: gateFailureEffectForMode(failureMode),
@@ -7472,7 +7594,7 @@ var applyGateDecision = (pi, ctx, session, result, reason, failureMode) => {
7472
7594
  usage: result.usage
7473
7595
  });
7474
7596
  updateAdvisorUsageStatus(ctx, session);
7475
- const failure = failureEffect(result.category, result.message, ctx, session, failureMode);
7597
+ const failure = failureEffect(result.category, result.message, ctx, session, failureMode, herdrBlock);
7476
7598
  sendAutomaticGateFailure(pi, `**Advisor gate failure (${result.category}):** ${result.message}`, snapshotAdvisorUsage(result.usage));
7477
7599
  return failure.block ? { block: true, reason: `${reason}
7478
7600
  ${failure.reason}` } : undefined;
@@ -7494,24 +7616,24 @@ ${failure.reason}` } : undefined;
7494
7616
  }
7495
7617
  const gateReason = `Advisor loop review: ${result.markdown}`;
7496
7618
  if (result.decision === "blocked") {
7497
- const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode);
7619
+ const effect = blockedDecisionEffect(gateReason, ctx, session, failureMode, herdrBlock);
7498
7620
  return effect.block ? { block: true, reason: effect.reason } : undefined;
7499
7621
  }
7500
7622
  return { block: true, reason: gateReason };
7501
7623
  };
7502
- var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus) => {
7624
+ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus, herdrActivity = herdrAdvisorActivity, herdrBlock = herdrAdvisorBlock) => {
7503
7625
  if (isSimpleMode() || event.toolName === "ask_advisor" || !advisorModelIsAllowed(ctx) || !advisorAutoLoopGateRef || !session.recordToolCall(event.toolName, event.input, advisorLoopThresholdRef)) {
7504
7626
  return;
7505
7627
  }
7506
7628
  const reason = `Advisor loop gate: normalized signature for ${event.toolName} repeated ${advisorLoopThresholdRef} times without a materially different tool action.`;
7507
7629
  const failureMode = advisorFailureModeRef;
7508
7630
  if (!session.canConsult(getAdvisorMaxCallsPerSession())) {
7509
- const failure = failureEffect("budget-exhausted", "Advisor gate call budget is exhausted.", ctx, session, failureMode);
7631
+ const failure = failureEffect("budget-exhausted", "Advisor gate call budget is exhausted.", ctx, session, failureMode, herdrBlock);
7510
7632
  return failure.block ? { block: true, reason: failure.reason } : undefined;
7511
7633
  }
7512
7634
  session.consumeCall();
7513
7635
  session.resetTurnsSinceConsultation();
7514
- herdrAdvisorActivity.start();
7636
+ const finishHerdrActivity = herdrActivity.start();
7515
7637
  let scoutDetails;
7516
7638
  const scoutStatusToken = Symbol("automatic-gate-scout");
7517
7639
  scoutStatus.register(scoutStatusToken);
@@ -7522,10 +7644,10 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
7522
7644
  gateCallSent = true;
7523
7645
  }
7524
7646
  };
7525
- if (!advisorScoutEnabledRef) {
7526
- ensureGateCall();
7527
- }
7528
7647
  try {
7648
+ if (!advisorScoutEnabledRef) {
7649
+ ensureGateCall();
7650
+ }
7529
7651
  const result = await runGate(ctx, `${reason} Review the repeated actions and recommend the smallest safe next step.`, "repeated-tool-call", ctx.signal, undefined, (scoutEvent) => {
7530
7652
  scoutStatus.update(ctx, scoutStatusToken, scoutEvent);
7531
7653
  scoutDetails = appendScoutLifecycleEntry(pi, scoutEvent, scoutDetails);
@@ -7534,10 +7656,10 @@ var handleAutomaticGate = async (pi, event, ctx, session, runGate, scoutStatus)
7534
7656
  }
7535
7657
  }, event.toolCallId);
7536
7658
  ensureGateCall();
7537
- return applyGateDecision(pi, ctx, session, result, reason, failureMode);
7659
+ return applyGateDecision(pi, ctx, session, result, reason, failureMode, herdrBlock);
7538
7660
  } finally {
7661
+ finishHerdrActivity();
7539
7662
  scoutStatus.release(ctx, scoutStatusToken);
7540
- herdrAdvisorActivity.finish();
7541
7663
  }
7542
7664
  };
7543
7665
 
@@ -7553,6 +7675,8 @@ var modelAccessBlock = (toolName, ctx) => {
7553
7675
  };
7554
7676
  };
7555
7677
  var registerToolLifecycle = ({
7678
+ herdrActivity,
7679
+ herdrBlock,
7556
7680
  pi,
7557
7681
  reservedCalls,
7558
7682
  runGate,
@@ -7579,7 +7703,7 @@ var registerToolLifecycle = ({
7579
7703
  pi.on("session_start", (_event, ctx) => {
7580
7704
  session.resetTask();
7581
7705
  reservedCalls.clear();
7582
- herdrAdvisorBlock.clear();
7706
+ herdrBlock.clear();
7583
7707
  if (ctx?.hasUI) {
7584
7708
  ctx.ui.setStatus("advisor-usage", undefined);
7585
7709
  }
@@ -7627,10 +7751,11 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
7627
7751
  if (event.toolName === "ask_advisor") {
7628
7752
  return reservation;
7629
7753
  }
7630
- return handleAutomaticGate(pi, event, ctx, session, runGate, scoutStatus);
7754
+ return handleAutomaticGate(pi, event, ctx, session, runGate, scoutStatus, herdrActivity, herdrBlock);
7631
7755
  });
7632
7756
  pi.on("agent_settled", (_event, ctx) => {
7633
7757
  reservedCalls.clear();
7758
+ session.clearCallReservations();
7634
7759
  if (isSimpleMode() || session.blocked || !advisorSessionSummaryRef) {
7635
7760
  return;
7636
7761
  }
@@ -7641,8 +7766,9 @@ ${guidelines.map((rule) => `- ${rule}`).join(`
7641
7766
  });
7642
7767
  pi.on("session_shutdown", (_event, ctx) => {
7643
7768
  reservedCalls.clear();
7769
+ session.clearCallReservations();
7644
7770
  scoutStatus.clear(ctx);
7645
- herdrAdvisorBlock.clear();
7771
+ herdrBlock.clear();
7646
7772
  if (ctx?.hasUI) {
7647
7773
  ctx.ui.setStatus("advisor-usage", undefined);
7648
7774
  }
@@ -7776,6 +7902,8 @@ var registerAdvisorTool = (pi, session = advisorSessionState, dependencies = {})
7776
7902
  const registration = {
7777
7903
  appendOutcome: dependencies.appendOutcome ?? appendOutcome,
7778
7904
  consult: dependencies.consult ?? consultAdvisor,
7905
+ herdrActivity: dependencies.herdrActivity ?? herdrAdvisorActivity,
7906
+ herdrBlock: dependencies.herdrBlock ?? herdrAdvisorBlock,
7779
7907
  pi,
7780
7908
  reservedCalls: new Set,
7781
7909
  runGate: dependencies.runGate ?? runAdvisorGate,
@@ -7790,6 +7918,7 @@ var registerAdvisorTool = (pi, session = advisorSessionState, dependencies = {})
7790
7918
  const turnGate = {
7791
7919
  activeTools: () => pi.getActiveTools(),
7792
7920
  consult: registration.consult,
7921
+ herdrActivity: registration.herdrActivity,
7793
7922
  send: (message) => pi.sendMessage(message, { deliverAs: "steer" }),
7794
7923
  session
7795
7924
  };
@@ -7806,11 +7935,17 @@ var runAdvisorGate2 = (...args) => runAdvisorGate(...args);
7806
7935
  function registerPiAdvisor(pi) {
7807
7936
  const sessionState = new AdvisorSessionState;
7808
7937
  const scoutStatus = new ScoutStatusManager;
7809
- setHerdrBlockedEmitter((active, label) => pi.events.emit("herdr:blocked", { active, label }));
7938
+ const herdrActivity = herdrAdvisorActivity.createScope();
7939
+ const herdrBlock = new HerdrAdvisorBlock(undefined, () => getAdvisorSettings().herdrIntegration, (active, label) => {
7940
+ pi.events.emit("herdr:blocked", { active, label });
7941
+ });
7810
7942
  registerAdvisorTool(pi, sessionState, {
7943
+ herdrActivity,
7944
+ herdrBlock,
7811
7945
  statusManager: scoutStatus
7812
7946
  });
7813
7947
  registerCommands(pi, {
7948
+ herdrActivity,
7814
7949
  sessionState,
7815
7950
  statusManager: scoutStatus
7816
7951
  });