@indexnetwork/protocol 14.3.2-rc.477.1 → 17.0.0-rc.479.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.
Files changed (184) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/IMPLEMENTATION.md +50 -52
  3. package/STABILITY.md +3 -3
  4. package/dist/chat/chat.agent.js +2 -3
  5. package/dist/chat/chat.graph.d.ts +41 -1
  6. package/dist/chat/chat.graph.js +108 -127
  7. package/dist/chat/negotiator.persona.d.ts +1 -1
  8. package/dist/chat/negotiator.persona.js +1 -4
  9. package/dist/chat/negotiator.prompt.js +1 -2
  10. package/dist/chat/onboarding.persona.js +0 -1
  11. package/dist/chat/reporter.persona.js +0 -1
  12. package/dist/chat/signal.persona.js +0 -1
  13. package/dist/contacts/application/contact.tools.d.ts +6 -9
  14. package/dist/contacts/application/contact.tools.js +11 -92
  15. package/dist/contacts/application/index.d.ts +2 -14
  16. package/dist/contacts/application/index.js +2 -13
  17. package/dist/contacts/domain/contact.types.d.ts +0 -23
  18. package/dist/contacts/domain/index.d.ts +2 -6
  19. package/dist/contacts/index.d.ts +1 -1
  20. package/dist/contacts/index.js +1 -1
  21. package/dist/contacts/ports/contact.repository.port.d.ts +5 -12
  22. package/dist/contacts/ports/contact.repository.port.js +4 -6
  23. package/dist/contacts/ports/contact.tools.port.d.ts +1 -3
  24. package/dist/contacts/ports/contact.tools.port.js +1 -2
  25. package/dist/contacts/ports/index.d.ts +1 -1
  26. package/dist/contacts/ports/index.js +1 -1
  27. package/dist/discovery/hyde.frame.d.ts +4 -4
  28. package/dist/discovery/hyde.graph.d.ts +71 -12
  29. package/dist/discovery/hyde.graph.js +337 -335
  30. package/dist/discovery/lens.inferrer.d.ts +6 -6
  31. package/dist/enrichment/enrichment.graph.d.ts +127 -9
  32. package/dist/enrichment/enrichment.graph.js +633 -655
  33. package/dist/enrichment/enrichment.state.d.ts +1 -1
  34. package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
  35. package/dist/enrichment/enrichment.tools.context-read.js +353 -0
  36. package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
  37. package/dist/enrichment/enrichment.tools.context-write.js +417 -0
  38. package/dist/enrichment/enrichment.tools.d.ts +11 -1
  39. package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
  40. package/dist/enrichment/enrichment.tools.helpers.js +234 -0
  41. package/dist/enrichment/enrichment.tools.js +16 -975
  42. package/dist/index.d.ts +27 -50
  43. package/dist/index.js +17 -28
  44. package/dist/intents/application/intent.graph.d.ts +71 -67
  45. package/dist/intents/application/intent.graph.execute.d.ts +59 -0
  46. package/dist/intents/application/intent.graph.execute.js +301 -0
  47. package/dist/intents/application/intent.graph.infer.d.ts +44 -0
  48. package/dist/intents/application/intent.graph.infer.js +97 -0
  49. package/dist/intents/application/intent.graph.js +96 -888
  50. package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
  51. package/dist/intents/application/intent.graph.reconcile.js +274 -0
  52. package/dist/intents/application/intent.graph.shared.d.ts +70 -0
  53. package/dist/intents/application/intent.graph.shared.js +153 -0
  54. package/dist/intents/domain/intent.state.d.ts +1 -1
  55. package/dist/maintenance/maintenance.graph.d.ts +66 -3
  56. package/dist/maintenance/maintenance.graph.js +155 -156
  57. package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
  58. package/dist/mcp/mcp.authorization-policy.js +14 -34
  59. package/dist/mcp/mcp.server.d.ts +3 -3
  60. package/dist/mcp/mcp.server.js +14 -20
  61. package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
  62. package/dist/negotiations/application/negotiation.candidates.js +162 -0
  63. package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
  64. package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
  65. package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
  66. package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
  67. package/dist/negotiations/application/negotiation.graph.init.js +227 -0
  68. package/dist/negotiations/application/negotiation.graph.js +69 -1388
  69. package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
  70. package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
  71. package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
  72. package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
  73. package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
  74. package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
  75. package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
  76. package/dist/negotiations/domain/negotiation.state.js +0 -1
  77. package/dist/networks/application/indexer.graph.d.ts +165 -7
  78. package/dist/networks/application/indexer.graph.js +339 -388
  79. package/dist/networks/application/indexer.state.d.ts +1 -1
  80. package/dist/networks/application/membership.graph.d.ts +83 -5
  81. package/dist/networks/application/membership.graph.js +177 -207
  82. package/dist/networks/application/network.graph.d.ts +148 -5
  83. package/dist/networks/application/network.graph.js +249 -278
  84. package/dist/networks/domain/membership.state.d.ts +1 -1
  85. package/dist/networks/domain/network.state.d.ts +1 -1
  86. package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
  87. package/dist/opportunities/application/delivery-card.cache.js +2 -2
  88. package/dist/opportunities/application/index.d.ts +2 -2
  89. package/dist/opportunities/application/index.js +2 -2
  90. package/dist/opportunities/application/opportunity.evaluator.js +1 -1
  91. package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
  92. package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
  93. package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
  94. package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
  95. package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
  96. package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
  97. package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
  98. package/dist/opportunities/application/opportunity.graph.js +120 -3690
  99. package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
  100. package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
  101. package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
  102. package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
  103. package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
  104. package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
  105. package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
  106. package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
  107. package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
  108. package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
  109. package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
  110. package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
  111. package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
  112. package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
  113. package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
  114. package/dist/opportunities/application/opportunity.tools.js +28 -711
  115. package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
  116. package/dist/opportunities/application/opportunity.tools.list.js +493 -0
  117. package/dist/opportunities/domain/index.d.ts +3 -3
  118. package/dist/opportunities/domain/index.js +3 -3
  119. package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
  120. package/dist/opportunities/index.d.ts +6 -6
  121. package/dist/opportunities/index.js +4 -4
  122. package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
  123. package/dist/opportunities/radar/radar.graph.d.ts +57 -13
  124. package/dist/opportunities/radar/radar.graph.js +470 -471
  125. package/dist/premises/premise.graph.d.ts +88 -20
  126. package/dist/premises/premise.graph.js +207 -218
  127. package/dist/questions/domain/question.schema.d.ts +40 -40
  128. package/dist/shared/agent/tool.factory.js +0 -8
  129. package/dist/shared/agent/tool.helpers.d.ts +21 -45
  130. package/dist/shared/agent/tool.helpers.js +1 -2
  131. package/dist/shared/agent/tool.registry.d.ts +4 -3
  132. package/dist/shared/agent/tool.registry.js +2 -4
  133. package/dist/shared/agent/tool.runtime.js +0 -2
  134. package/dist/shared/agent/utility.tools.js +9 -13
  135. package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
  136. package/dist/shared/interfaces/database.capabilities.js +7 -0
  137. package/dist/shared/interfaces/database.entities.d.ts +533 -0
  138. package/dist/shared/interfaces/database.entities.js +10 -0
  139. package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
  140. package/dist/shared/interfaces/database.identity-queries.js +4 -0
  141. package/dist/shared/interfaces/database.interface.d.ts +15 -2277
  142. package/dist/shared/interfaces/database.interface.js +8 -0
  143. package/dist/shared/interfaces/database.member-queries.d.ts +221 -0
  144. package/dist/shared/interfaces/database.member-queries.js +4 -0
  145. package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
  146. package/dist/shared/interfaces/database.negotiation.js +26 -0
  147. package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
  148. package/dist/shared/interfaces/database.network-queries.js +4 -0
  149. package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
  150. package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
  151. package/dist/shared/interfaces/database.port.d.ts +322 -0
  152. package/dist/shared/interfaces/database.port.js +29 -0
  153. package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
  154. package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
  155. package/package.json +2 -15
  156. package/dist/contacts/application/contact.inviter.d.ts +0 -49
  157. package/dist/contacts/application/contact.inviter.js +0 -64
  158. package/dist/integrations/application/index.d.ts +0 -14
  159. package/dist/integrations/application/index.js +0 -14
  160. package/dist/integrations/application/integration.tools.d.ts +0 -27
  161. package/dist/integrations/application/integration.tools.js +0 -98
  162. package/dist/integrations/domain/index.d.ts +0 -16
  163. package/dist/integrations/domain/index.js +0 -1
  164. package/dist/integrations/domain/integration.types.d.ts +0 -51
  165. package/dist/integrations/domain/integration.types.js +0 -9
  166. package/dist/integrations/index.d.ts +0 -10
  167. package/dist/integrations/index.js +0 -8
  168. package/dist/integrations/ports/index.d.ts +0 -25
  169. package/dist/integrations/ports/index.js +0 -23
  170. package/dist/integrations/ports/integration.adapter.port.d.ts +0 -62
  171. package/dist/integrations/ports/integration.adapter.port.js +0 -15
  172. package/dist/integrations/ports/integration.importer.port.d.ts +0 -31
  173. package/dist/integrations/ports/integration.importer.port.js +0 -15
  174. package/dist/integrations/ports/integration.tools.port.d.ts +0 -25
  175. package/dist/integrations/ports/integration.tools.port.js +0 -18
  176. package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
  177. package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
  178. package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
  179. package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
  180. package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
  181. package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
  182. package/dist/opportunities/domain/opportunity.presentation.js +0 -516
  183. package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
  184. package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Helpers shared by the enrichment tools.
3
+ *
4
+ * These were nested functions inside `createEnrichmentTools`, closing over the
5
+ * destructured host capabilities. They are top-level now, each taking the tool
6
+ * deps explicitly, so the three tool modules can share them.
7
+ */
8
+ import { z } from "zod";
9
+ import { requestContext } from "../shared/observability/request-context.js";
10
+ import { protocolLogger } from "../shared/observability/protocol.logger.js";
11
+ import { socialsToEnrichmentRequest } from "../shared/utils/social-label.js";
12
+ import { normalizeTelegramHandle } from "../shared/utils/telegram-handle.js";
13
+ import { focusedNetworkId } from "../shared/agent/tool.scope.js";
14
+ export const logger = protocolLogger("ChatTools:Enrichment");
15
+ export function isMeaningfulEnrichment(enrichment) {
16
+ return !!enrichment &&
17
+ enrichment.confidentMatch &&
18
+ (enrichment.identity.bio.trim().length > 0 ||
19
+ enrichment.narrative.context.trim().length > 0 ||
20
+ enrichment.attributes.skills.length > 0 ||
21
+ enrichment.attributes.interests.length > 0);
22
+ }
23
+ export const approvedProfileDraftSchema = z.object({
24
+ identity: z.object({ name: z.string(), bio: z.string(), location: z.string() }),
25
+ narrative: z.object({ context: z.string() }),
26
+ attributes: z.object({ interests: z.array(z.string()), skills: z.array(z.string()) }),
27
+ });
28
+ export function trimToUndefined(value) {
29
+ const trimmed = value?.trim();
30
+ return trimmed && trimmed.length > 0 ? trimmed : undefined;
31
+ }
32
+ export function isPlaceholderName(value) {
33
+ const normalized = value.trim().toLowerCase();
34
+ return normalized === 'unknown' || normalized === 'user';
35
+ }
36
+ export async function enrichFromUserRecord(deps, user) {
37
+ const enrichmentSocials = socialsToEnrichmentRequest(user.socials);
38
+ return deps.enricher.enrichUserProfile({
39
+ name: trimToUndefined(user.name),
40
+ email: trimToUndefined(user.email),
41
+ linkedin: enrichmentSocials.linkedin || undefined,
42
+ twitter: enrichmentSocials.twitter || undefined,
43
+ github: enrichmentSocials.github || undefined,
44
+ telegram: enrichmentSocials.telegram || undefined,
45
+ websites: enrichmentSocials.websites?.length ? enrichmentSocials.websites : undefined,
46
+ });
47
+ }
48
+ export function selectProfileSeed(onboarding, networkId) {
49
+ const seeds = onboarding?.profileSeeds ?? [];
50
+ if (seeds.length === 0)
51
+ return undefined;
52
+ const scoped = networkId ? seeds.filter((seed) => seed.networkId === networkId) : seeds;
53
+ return scoped[scoped.length - 1];
54
+ }
55
+ export function normalizeSocialUpdate(label, value) {
56
+ const normalizedLabel = label.trim().toLowerCase();
57
+ if (!normalizedLabel)
58
+ return null;
59
+ const trimmedValue = value.trim();
60
+ if (!trimmedValue)
61
+ return null;
62
+ if (normalizedLabel === 'telegram') {
63
+ const handle = normalizeTelegramHandle(trimmedValue);
64
+ return handle ? { label: normalizedLabel, value: handle } : null;
65
+ }
66
+ return { label: normalizedLabel, value: trimmedValue };
67
+ }
68
+ export async function mergeUserSocials(deps, incoming) {
69
+ const normalizedIncoming = incoming
70
+ .map((social) => normalizeSocialUpdate(social.label, social.value))
71
+ .filter((social) => social !== null);
72
+ if (normalizedIncoming.length === 0)
73
+ return;
74
+ const existingSocials = await deps.userDb.getUserSocials();
75
+ const incomingLabels = new Set(normalizedIncoming.map((social) => social.label));
76
+ const kept = existingSocials
77
+ .filter((social) => !incomingLabels.has(social.label) || social.label === 'custom')
78
+ .map((social) => ({ label: social.label, value: social.value }));
79
+ const merged = incomingLabels.has('custom')
80
+ ? [...kept.filter((social) => social.label !== 'custom'), ...normalizedIncoming]
81
+ : [...kept, ...normalizedIncoming];
82
+ await deps.userDb.setUserSocials(merged);
83
+ }
84
+ export function socialsRecordToRows(socials) {
85
+ if (!socials)
86
+ return [];
87
+ return Object.entries(socials).map(([label, value]) => ({ label, value }));
88
+ }
89
+ export async function enqueueEnrichmentRun(deps, context, operation, input) {
90
+ if (!context.isMcp || !deps.enrichmentRuns || !deps.enrichmentRunQueue)
91
+ return null;
92
+ const run = await deps.enrichmentRuns.create({
93
+ userId: context.userId,
94
+ agentId: context.agentId ?? null,
95
+ operation,
96
+ input,
97
+ context: {
98
+ userId: context.userId,
99
+ userName: context.userName,
100
+ userEmail: context.userEmail,
101
+ ...(focusedNetworkId(context) ? { scopeType: 'network', scopeId: focusedNetworkId(context) } : {}),
102
+ ...(context.indexName ? { indexName: context.indexName } : {}),
103
+ ...(context.sessionId ? { sessionId: context.sessionId } : {}),
104
+ ...(context.agentId ? { agentId: context.agentId } : {}),
105
+ },
106
+ });
107
+ try {
108
+ await deps.enrichmentRunQueue.enqueue(run.id);
109
+ }
110
+ catch (err) {
111
+ const message = err instanceof Error ? err.message : String(err);
112
+ await deps.enrichmentRuns.markFailed(run.id, message);
113
+ if (err instanceof Error)
114
+ throw err;
115
+ const wrapped = new Error(`Failed to enqueue profile run: ${message}`);
116
+ wrapped.cause = err;
117
+ throw wrapped;
118
+ }
119
+ return run.id;
120
+ }
121
+ export async function persistApprovedProfileContext(deps, profile, user, networkId) {
122
+ await deps.userDb.updateUser({
123
+ name: profile.identity.name,
124
+ intro: profile.identity.bio,
125
+ location: profile.identity.location,
126
+ });
127
+ const onboarding = user?.onboarding ?? undefined;
128
+ const seed = selectProfileSeed(onboarding, networkId);
129
+ if (!seed?.socials?.length)
130
+ return;
131
+ await mergeUserSocials(deps, seed.socials);
132
+ }
133
+ export async function markApprovedProfileConfirmed(deps, context) {
134
+ const latestUser = await deps.userDb.getUser();
135
+ const currentOnboarding = latestUser?.onboarding ?? context.user.onboarding ?? {};
136
+ await deps.userDb.updateUser({
137
+ onboarding: {
138
+ ...currentOnboarding,
139
+ profileConfirmedAt: currentOnboarding.profileConfirmedAt ?? new Date().toISOString(),
140
+ currentStep: currentOnboarding.completedAt
141
+ ? currentOnboarding.currentStep ?? 'complete'
142
+ : 'first_signal',
143
+ },
144
+ });
145
+ }
146
+ export function buildProfileInput(parts) {
147
+ const lines = [];
148
+ if (parts.name)
149
+ lines.push(`Name: ${parts.name}`);
150
+ if (parts.location)
151
+ lines.push(`Location: ${parts.location}`);
152
+ if (parts.bioOrDescription)
153
+ lines.push(parts.bioOrDescription);
154
+ if (parts.socials?.length) {
155
+ lines.push(`User-provided public links:\n${parts.socials.map((s) => `${s.label}: ${s.value}`).join('\n')}`);
156
+ }
157
+ return lines.filter((line) => line.trim().length > 0).join('\n\n');
158
+ }
159
+ export function toProfileSummary(profile) {
160
+ return {
161
+ name: profile.identity.name,
162
+ bio: profile.identity.bio,
163
+ location: profile.identity.location,
164
+ skills: profile.attributes.skills,
165
+ interests: profile.attributes.interests,
166
+ };
167
+ }
168
+ export function buildApprovedDraftProfileInput(draft) {
169
+ return [
170
+ draft.identity.name ? `My name is ${draft.identity.name}.` : '',
171
+ draft.identity.location ? `I am based in ${draft.identity.location}.` : '',
172
+ draft.identity.bio || '',
173
+ draft.narrative.context || '',
174
+ draft.attributes.skills.length ? `My skills include ${draft.attributes.skills.join(', ')}.` : '',
175
+ draft.attributes.interests.length ? `My interests include ${draft.attributes.interests.join(', ')}.` : '',
176
+ ].filter((part) => part.trim().length > 0).join('\n');
177
+ }
178
+ export async function decomposeApprovedDraftProfile(deps, profile) {
179
+ const input = buildApprovedDraftProfileInput(profile);
180
+ if (!input.trim())
181
+ return;
182
+ const traceEmitter = requestContext.getStore()?.traceEmitter;
183
+ const graphStart = Date.now();
184
+ traceEmitter?.({ type: "graph_start", name: "enrichment" });
185
+ try {
186
+ const graphInput = {
187
+ userId: profile.userId,
188
+ operationMode: 'write',
189
+ input,
190
+ forceUpdate: true,
191
+ };
192
+ // Always invoked as a background fire-and-forget task (see confirm_user_context
193
+ // call sites), so decomposition must outlive the originating request — invoke
194
+ // the graph directly and never bind the request abort signal, which would
195
+ // cancel it as soon as the web request completes.
196
+ const result = await deps.graphs.profile.invoke(graphInput);
197
+ if (result.error) {
198
+ const err = new Error(result.error);
199
+ logger.error('Approved draft premise decomposition failed', {
200
+ userId: profile.userId,
201
+ error: result.error,
202
+ });
203
+ deps.reportToolError?.(err, {
204
+ subsystem: 'enrichment',
205
+ operation: 'profile.confirm_draft_decompose',
206
+ toolName: 'confirm_user_context',
207
+ userId: profile.userId,
208
+ tags: { toolName: 'confirm_user_context', execution: 'background' },
209
+ });
210
+ return;
211
+ }
212
+ // The write graph's decompose → aggregate → generate → save_profile
213
+ // pipeline persists the aggregate profile. The approved draft was already
214
+ // saved before decomposition started, so the DB is consistent regardless
215
+ // of graph outcome. Do not re-save here — the graph's save_profile is
216
+ // authoritative, and a concurrent user-driven profile update could race.
217
+ }
218
+ catch (err) {
219
+ logger.error('Approved draft premise decomposition failed', {
220
+ userId: profile.userId,
221
+ error: err instanceof Error ? err.message : String(err),
222
+ });
223
+ deps.reportToolError?.(err, {
224
+ subsystem: 'enrichment',
225
+ operation: 'profile.confirm_draft_decompose',
226
+ toolName: 'confirm_user_context',
227
+ userId: profile.userId,
228
+ tags: { toolName: 'confirm_user_context', execution: 'background' },
229
+ });
230
+ }
231
+ finally {
232
+ traceEmitter?.({ type: "graph_end", name: "enrichment", durationMs: Date.now() - graphStart });
233
+ }
234
+ }