@peopl-health/nexus 5.68.8 → 5.68.9

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.
@@ -65,4 +65,4 @@ function clearCohortCache() {
65
65
  inFlightRead = null;
66
66
  }
67
67
 
68
- module.exports = { isInCohort, clearCohortCache };
68
+ module.exports = { isInCohort, isCohortEnabled, clearCohortCache };
@@ -56,6 +56,7 @@ class ToolRuntimeContext {
56
56
  this.skillActivationIteration = new Map();
57
57
  this.currentIteration = null;
58
58
  this.toolHistory = [];
59
+ this.toolRejections = [];
59
60
  this.safetyArtifact = null;
60
61
  this.palliativeAssessment = null;
61
62
  this.careplan = null;
@@ -93,9 +94,11 @@ class ToolRuntimeContext {
93
94
  this.trace.addStep({ iteration, ...toolData });
94
95
  if (toolData.success) this.toolHistory.push(toolData);
95
96
  if (!toolData.success || isToolFailure(toolData.tool_output)) {
97
+ const code = refusalCode(toolData.tool_output);
98
+ this.toolRejections.push({ tool: toolData.tool_name, code, iteration: iteration ?? null });
96
99
  logger.warn('[toolRuntime] tool call rejected', {
97
100
  tool: toolData.tool_name,
98
- reason: refusalCode(toolData.tool_output),
101
+ reason: code,
99
102
  iteration: iteration ?? null,
100
103
  turnId: this.turnId || null,
101
104
  });
@@ -108,7 +111,7 @@ class ToolRuntimeContext {
108
111
  }
109
112
 
110
113
  async store({ outbound = null, completed = false, predictionTimeMs = null, error = null } = {}) {
111
- this.trace.setSignals({ activatedSkills: [...this.activatedSkills].sort() });
114
+ this.trace.setSignals({ activatedSkills: [...this.activatedSkills].sort(), toolRejections: [...this.toolRejections] });
112
115
  this.trace.setOutbound(outbound || this.trace.outbound || null);
113
116
  const hasError = error !== null && error !== undefined;
114
117
  const isSuperseded = hasError ? false : !this.isLive();
@@ -0,0 +1,59 @@
1
+ const { isCohortEnabled } = require('../config/cohortConfig');
2
+ const { isBridgeEnabled } = require('./bridgeConfig');
3
+ const { contextReadLive, contextMirrorLive, contextVerifierLive } = require('./clinicalContextFlags');
4
+ const { clinicalPatternFloorLive } = require('./clinicalPatternFloorFlags');
5
+ const { isComposerEnabled, isAgentSelfRenderEnabled } = require('./composerFlags');
6
+ const { isContingencyCancelOnReplyLive, isArmOnOpenCeilingLive, isContingencyRuleBlockLive } = require('./contingencyFlags');
7
+ const { escalationAssessmentLive, escalationUpdateModeLive } = require('./escalationFlags');
8
+ const { inboundRecoveryLive, coldInboundMode } = require('./inboundRecoveryFlags');
9
+ const { isLlmAuditEnabled } = require('./llmAuditConfig');
10
+ const { isLlmMonitorEnabled } = require('./llmMonitorConfig');
11
+ const { patternConsultLive } = require('./patternConsultFlags');
12
+ const { recommendationRetryLive } = require('./recommendationRecoveryFlags');
13
+ const { isShadowEnabled } = require('./shadowConfig');
14
+ const { supportLightFromChatLive } = require('./supportLightFlags');
15
+ const { triageGateFloorLive } = require('./triageGateFloorFlags');
16
+ const { triageRecommendationLive, rankComparatorLive } = require('./triageRecommendationFlags');
17
+ const { isConversationalDeclarationLive, isConversationalDraftReuseLive, isDeclarationModelCheckLive } = require('./turnEfficiencyFlags');
18
+
19
+ const FLAG_READERS = {
20
+ cohort: isCohortEnabled,
21
+ bridge: isBridgeEnabled,
22
+ contextRead: contextReadLive,
23
+ contextMirror: contextMirrorLive,
24
+ contextVerifier: contextVerifierLive,
25
+ clinicalPatternFloor: clinicalPatternFloorLive,
26
+ composer: isComposerEnabled,
27
+ agentSelfRender: isAgentSelfRenderEnabled,
28
+ contingencyCancelOnReply: isContingencyCancelOnReplyLive,
29
+ armOnOpenCeiling: isArmOnOpenCeilingLive,
30
+ contingencyRuleBlock: isContingencyRuleBlockLive,
31
+ escalationAssessment: escalationAssessmentLive,
32
+ escalationUpdateMode: escalationUpdateModeLive,
33
+ inboundRecovery: inboundRecoveryLive,
34
+ coldInboundMode,
35
+ llmAudit: isLlmAuditEnabled,
36
+ llmMonitor: isLlmMonitorEnabled,
37
+ patternConsult: patternConsultLive,
38
+ recommendationRetry: recommendationRetryLive,
39
+ shadow: isShadowEnabled,
40
+ supportLightFromChat: supportLightFromChatLive,
41
+ triageGateFloor: triageGateFloorLive,
42
+ triageRecommendation: triageRecommendationLive,
43
+ rankComparator: rankComparatorLive,
44
+ conversationalDeclaration: isConversationalDeclarationLive,
45
+ conversationalDraftReuse: isConversationalDraftReuseLive,
46
+ declarationModelCheck: isDeclarationModelCheckLive,
47
+ };
48
+
49
+ function snapshotClinicalFlags() {
50
+ return Object.fromEntries(Object.entries(FLAG_READERS).map(([name, read]) => {
51
+ try {
52
+ return [name, read()];
53
+ } catch (error) {
54
+ return [name, null];
55
+ }
56
+ }));
57
+ }
58
+
59
+ module.exports = { snapshotClinicalFlags };
@@ -240,7 +240,7 @@ class AnthropicProvider extends BaseLLMProvider {
240
240
  if (lastUserTurn?.role === 'user') {
241
241
  deliveryNudges++;
242
242
  previousRoundTools = [];
243
- trace?.setSignals?.({ deliveryNudges });
243
+ trace?.setSignals?.({ deliveryNudges, nudgeKind: canDecide ? 'outreach' : 'delivery' });
244
244
  logger.warn('[AnthropicProvider] Turn will not close — nudging', { tool: canDecide ? OUTREACH_DECISION_TOOL : 'DeliverPatientMessage', deliveryNudges, hasEnded, isBudgetSpent });
245
245
  const turnContent = Array.isArray(lastUserTurn.content)
246
246
  ? lastUserTurn.content
@@ -10,6 +10,7 @@ const { resolveSkillCatalog } = require('../services/skillService');
10
10
  const { RISK_HEADLINE_MARKER } = require('../services/riskHeadlineService');
11
11
  const { resolveClinicalContext } = require('../services/clinicalContextReadService');
12
12
  const { isContingencyRuleBlockLive } = require('../flags/contingencyFlags');
13
+ const { snapshotClinicalFlags } = require('../flags/flagSnapshot');
13
14
  const { TURN_KIND } = require('../context/turnContext');
14
15
  const { extractPatientMessage } = require('../../helpers/patientMessageHelper');
15
16
  const runtimeConfig = require('../../config/runtimeConfig');
@@ -102,7 +103,7 @@ class BaseLLMProvider {
102
103
  presetId: resolvedPresetId, presetVersion: resolvedPresetVersion, modelConfig,
103
104
  } = await composePrompt({ presetId, presetVersion, promptId: assistantId, variables: promptVariables });
104
105
 
105
- const { toolIds, filtered, descriptions: toolDescriptions } = await resolveTools({
106
+ const { toolIds, filtered, descriptions: toolDescriptions, unregisteredToolIds = [] } = await resolveTools({
106
107
  promptId: resolvedPromptId || assistantId, assistant, presetToolIds,
107
108
  });
108
109
  const requestedAdditionalToolIds = Array.isArray(prePromptResult?.additionalToolIds)
@@ -200,7 +201,12 @@ class BaseLLMProvider {
200
201
  }
201
202
 
202
203
  trace?.setPrompt({ resolvedPresetId, resolvedPresetVersion });
203
- trace?.setSignals?.({ offeredToolIds: effectiveToolIds, enabledSkills });
204
+ trace?.setSignals?.({
205
+ offeredToolIds: effectiveToolIds,
206
+ enabledSkills,
207
+ flags: snapshotClinicalFlags(),
208
+ ...(unregisteredToolIds.length > 0 && { unregisteredToolIds }),
209
+ });
204
210
 
205
211
  const { response, toolsExecuted, retries, usage } = await this._invokeModel({
206
212
  instructions: instructions || devContent || '',
@@ -191,6 +191,7 @@ async function fetchToolsByRecordIds(recordIds) {
191
191
  return tools;
192
192
  }
193
193
 
194
+ const BLANK_TOOL_ID = '(blank tool_id)';
194
195
  const warnedUnlinked = new Set();
195
196
 
196
197
  function warnUnlinkedRegisteredTools(linkedToolIds, promptId) {
@@ -228,6 +229,7 @@ async function resolveTools({ promptId, assistant, presetToolIds = null, status
228
229
  const activeTools = mappedTools.filter(t => !t.status || t.status === 'prod' || (!isProd && t.status === 'dev'));
229
230
 
230
231
  const validToolIds = [];
232
+ const unregisteredToolIds = [];
231
233
  const descriptions = {};
232
234
 
233
235
  for (const tool of activeTools) {
@@ -237,6 +239,7 @@ async function resolveTools({ promptId, assistant, presetToolIds = null, status
237
239
  // An Airtable column retyped away from long text would otherwise reach the model as [object Object].
238
240
  if (typeof tool.description === 'string' && tool.description) descriptions[id] = tool.description;
239
241
  } else {
242
+ unregisteredToolIds.push(id || BLANK_TOOL_ID);
240
243
  logger.error('[promptComposer] Tool mapped in Airtable but not registered in code', { toolId: id, promptId });
241
244
  }
242
245
  }
@@ -252,7 +255,7 @@ async function resolveTools({ promptId, assistant, presetToolIds = null, status
252
255
 
253
256
  logger.debug('[promptComposer] Tools resolved', { promptId, mapped: activeTools.length, valid: validToolIds.length, filtered: true });
254
257
 
255
- return { toolIds: validToolIds, filtered: true, descriptions };
258
+ return { toolIds: validToolIds, filtered: true, descriptions, unregisteredToolIds };
256
259
  }
257
260
 
258
261
  function clearCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "5.68.8",
3
+ "version": "5.68.9",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",