@peopl-health/nexus 5.11.0-dev.1125 → 5.11.0-dev.1126

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.
@@ -45,7 +45,11 @@ class AssistantProcessor {
45
45
 
46
46
  if (thread.isInCohort && !runOptions.toolRuntimeContext && runOptions.runId && thread.code) {
47
47
  try {
48
- runOptions.toolRuntimeContext = new ToolRuntimeContext({ patientCode: thread.code, turnId: runOptions.runId });
48
+ runOptions.toolRuntimeContext = new ToolRuntimeContext({
49
+ patientCode: thread.code,
50
+ turnId: runOptions.runId,
51
+ shouldContinue: runOptions.shouldContinue,
52
+ });
49
53
  } catch (error) {
50
54
  logger.warn('[AssistantProcessor] turn will run untraced', { code: thread.code, error: error.message });
51
55
  }
@@ -83,7 +87,6 @@ class AssistantProcessor {
83
87
  outbound: output,
84
88
  completed: runResult?.completed,
85
89
  predictionTimeMs,
86
- shouldContinue: runOptions.shouldContinue,
87
90
  });
88
91
  } catch (error) {
89
92
  logger.warn('[AssistantProcessor] trace not stored', {
@@ -8,6 +8,7 @@ class ToolRuntimeContext {
8
8
  repos = {},
9
9
  contextSource = null,
10
10
  shadow = false,
11
+ shouldContinue = null,
11
12
  } = {}) {
12
13
  if (typeof patientCode !== 'string' || !patientCode) {
13
14
  throw new Error('ToolRuntimeContext requires a non-empty patientCode');
@@ -22,6 +23,7 @@ class ToolRuntimeContext {
22
23
  this.patientCode = patientCode;
23
24
  this.turnId = turnId;
24
25
  this.sessionId = sessionId;
26
+ this.shouldContinue = shouldContinue;
25
27
  this.repos = repos;
26
28
  this.contextSource = contextSource;
27
29
  this.enabledSkills = [];
@@ -42,13 +44,17 @@ class ToolRuntimeContext {
42
44
  return toolData;
43
45
  }
44
46
 
45
- async store({ outbound = null, completed = false, predictionTimeMs = null, shouldContinue = null } = {}) {
47
+ isLive() {
48
+ return typeof this.shouldContinue !== 'function' || this.shouldContinue();
49
+ }
50
+
51
+ async store({ outbound = null, completed = false, predictionTimeMs = null } = {}) {
46
52
  this.trace.setSignals({ activatedSkills: [...this.activatedSkills].sort() });
47
53
  this.trace.setOutbound(outbound);
48
- this.trace.setStatus(completed ? 'completed' : 'incomplete');
54
+ const superseded = !this.isLive();
55
+ this.trace.setStatus(superseded ? 'superseded' : (completed ? 'completed' : 'incomplete'));
49
56
  this.trace.setTimings({ predictionTimeMs });
50
- const superseded = shouldContinue && !shouldContinue();
51
- if (!superseded) await this.trace.save();
57
+ await this.trace.save();
52
58
  }
53
59
 
54
60
  setSafetyArtifact(artifact) {
@@ -21,16 +21,21 @@ const INTAKE_SOURCE_TO_AIRTABLE = {
21
21
  team_relay: 'doctor',
22
22
  };
23
23
 
24
- async function auditedWrite({ trace, code, table, write }) {
24
+ async function auditedWrite({ trace, code, table, write, isLive = null }) {
25
25
  const entry = { table, at: new Date().toISOString(), ok: false };
26
- try {
27
- const result = await write();
28
- entry.ok = true;
29
- const record = Array.isArray(result) ? result[0] : result;
30
- if (record?.id) entry.recordId = record.id;
31
- } catch (err) {
32
- entry.error = err?.message || String(err);
33
- logger.error('[clinicalAirtableService] Airtable write failed', { table, code, error: entry.error });
26
+ if (isLive && !isLive()) {
27
+ entry.skipped = 'superseded';
28
+ logger.info('[clinicalAirtableService] Airtable write skipped — turn superseded', { table, code });
29
+ } else {
30
+ try {
31
+ const result = await write();
32
+ entry.ok = true;
33
+ const record = Array.isArray(result) ? result[0] : result;
34
+ if (record?.id) entry.recordId = record.id;
35
+ } catch (err) {
36
+ entry.error = err?.message || String(err);
37
+ logger.error('[clinicalAirtableService] Airtable write failed', { table, code, error: entry.error });
38
+ }
34
39
  }
35
40
  if (trace?.setSignals) {
36
41
  const existing = Array.isArray(trace.signals?.airtableWrites) ? trace.signals.airtableWrites : [];
@@ -92,10 +97,11 @@ async function recordEmergency({ code, trace, symptom, suspectedCondition = '',
92
97
  });
93
98
  }
94
99
 
95
- async function recordClinicalIntake({ code, trace, messageRaw, source = 'self' }) {
100
+ async function recordClinicalIntake({ code, trace, messageRaw, source = 'self', isLive = null }) {
96
101
  return auditedWrite({
97
102
  trace,
98
103
  code,
104
+ isLive,
99
105
  table: 'intake',
100
106
  write: async () => {
101
107
  const patientId = await findIntakePatientRecordId(code);
@@ -110,12 +116,13 @@ async function recordClinicalIntake({ code, trace, messageRaw, source = 'self' }
110
116
  });
111
117
  }
112
118
 
113
- async function recordReminder({ code, trace, actionType, reminderId, reminderDateUtc, reminderType, description }) {
119
+ async function recordReminder({ code, trace, actionType, reminderId, reminderDateUtc, reminderType, description, isLive = null }) {
114
120
  const type = REMINDER_TYPE_TO_AIRTABLE[reminderType] || reminderType;
115
121
  if (actionType === 'update') {
116
122
  return auditedWrite({
117
123
  trace,
118
124
  code,
125
+ isLive,
119
126
  table: 'reminder',
120
127
  write: async () => {
121
128
  const updated = await airtableService.updateRecordByFilter(
@@ -133,6 +140,7 @@ async function recordReminder({ code, trace, actionType, reminderId, reminderDat
133
140
  return auditedWrite({
134
141
  trace,
135
142
  code,
143
+ isLive,
136
144
  table: 'reminder',
137
145
  write: () => airtableService.addLinkedRecord(
138
146
  Monitoreo_ID,
@@ -27,7 +27,7 @@ async function runShadowTurn({
27
27
  const shadowAssistant = resolveAssistant(shadowThread);
28
28
 
29
29
  const context = new ToolRuntimeContext({
30
- patientCode: code, turnId: runId, sessionId, shadow: true,
30
+ patientCode: code, turnId: runId, sessionId, shadow: true, shouldContinue,
31
31
  });
32
32
 
33
33
  const runOptions = {
@@ -153,6 +153,7 @@ async function handler(args, context = {}) {
153
153
  trace,
154
154
  messageRaw: intakeSummary,
155
155
  source: intakeSource,
156
+ isLive: runtime.isLive?.bind(runtime),
156
157
  });
157
158
  }
158
159
 
@@ -107,11 +107,18 @@ async function handler(args = {}, context = {}) {
107
107
  };
108
108
  trace.setSignals({ reminderIntents: [...existing, intent] });
109
109
 
110
+ const isLive = runtime.isLive?.bind(runtime);
111
+ const superseded = Boolean(isLive) && !isLive();
112
+
110
113
  if (runtime.patientCode) {
111
- try {
112
- await dispatchReminder({ intent, patientId: runtime.patientCode });
113
- } catch (err) {
114
- logger.warn('[schedulePatientReminder] reminder FHIR projection failed; ack unaffected', { turnId, error: err?.message });
114
+ if (superseded) {
115
+ logger.info('[schedulePatientReminder] reminder projection skipped — turn superseded', { turnId });
116
+ } else {
117
+ try {
118
+ await dispatchReminder({ intent, patientId: runtime.patientCode });
119
+ } catch (err) {
120
+ logger.warn('[schedulePatientReminder] reminder FHIR projection failed; ack unaffected', { turnId, error: err?.message });
121
+ }
115
122
  }
116
123
  }
117
124
 
@@ -124,6 +131,7 @@ async function handler(args = {}, context = {}) {
124
131
  reminderDateUtc: fireMoment.toISOString(),
125
132
  reminderType,
126
133
  description: storedDescription,
134
+ isLive,
127
135
  });
128
136
  }
129
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "5.11.0-dev.1125",
3
+ "version": "5.11.0-dev.1126",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",