@mindot/will 0.1.0

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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +777 -0
  3. package/dist/index.d.ts +8144 -0
  4. package/dist/index.js +26231 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +73 -0
  7. package/src/cognition/agency/access.grants.ts +59 -0
  8. package/src/cognition/agency/competence.codec.ts +89 -0
  9. package/src/cognition/agency/engines/action.selector.ts +462 -0
  10. package/src/cognition/agency/engines/affordance.synthesizer.ts +384 -0
  11. package/src/cognition/agency/engines/deliberation.engine.ts +241 -0
  12. package/src/cognition/agency/engines/instruction.intake.ts +54 -0
  13. package/src/cognition/agency/engines/motor.schema.executor.ts +532 -0
  14. package/src/cognition/agency/engines/reafference.engine.ts +241 -0
  15. package/src/cognition/agency/execution.primitives.ts +118 -0
  16. package/src/cognition/agency/index.ts +58 -0
  17. package/src/cognition/agency/proactive.communicator.ts +274 -0
  18. package/src/cognition/agency/reconcile.learning.ts +82 -0
  19. package/src/cognition/agency/schemas/external.ts +59 -0
  20. package/src/cognition/agency/schemas/innate.ts +111 -0
  21. package/src/cognition/agency/schemas/repertoire.ts +246 -0
  22. package/src/cognition/agency/selection.scoring.ts +191 -0
  23. package/src/cognition/agency/types.ts +165 -0
  24. package/src/cognition/bus.ts +391 -0
  25. package/src/cognition/completion.inbox.ts +86 -0
  26. package/src/cognition/config.mirror.entities.ts +475 -0
  27. package/src/cognition/conversation.memory.ts +81 -0
  28. package/src/cognition/event.log.ts +116 -0
  29. package/src/cognition/event.schemas.ts +568 -0
  30. package/src/cognition/faculties/aesthetic.evaluator.ts +313 -0
  31. package/src/cognition/faculties/affective.blender.ts +562 -0
  32. package/src/cognition/faculties/attachment.evaluator.ts +432 -0
  33. package/src/cognition/faculties/attention.allocator.ts +422 -0
  34. package/src/cognition/faculties/autobiographical.narrator.ts +300 -0
  35. package/src/cognition/faculties/bias.detector.ts +363 -0
  36. package/src/cognition/faculties/circadian.oscillator.ts +294 -0
  37. package/src/cognition/faculties/confidence.calibrator.ts +324 -0
  38. package/src/cognition/faculties/dream.simulator.ts +251 -0
  39. package/src/cognition/faculties/empathy.simulator.ts +253 -0
  40. package/src/cognition/faculties/energy.regulator.ts +320 -0
  41. package/src/cognition/faculties/episodic.consolidator.ts +728 -0
  42. package/src/cognition/faculties/executive.engine/commands.ts +459 -0
  43. package/src/cognition/faculties/executive.engine/config.ts +46 -0
  44. package/src/cognition/faculties/executive.engine/context.ts +605 -0
  45. package/src/cognition/faculties/executive.engine/deferred.effects.ts +104 -0
  46. package/src/cognition/faculties/executive.engine/deliberate.reasoning.ts +60 -0
  47. package/src/cognition/faculties/executive.engine/effort.gate.ts +114 -0
  48. package/src/cognition/faculties/executive.engine/engine.ts +1028 -0
  49. package/src/cognition/faculties/executive.engine/escalation.buffer.ts +90 -0
  50. package/src/cognition/faculties/executive.engine/facet.supervisor.ts +271 -0
  51. package/src/cognition/faculties/executive.engine/facet.ts +628 -0
  52. package/src/cognition/faculties/executive.engine/gating.ts +222 -0
  53. package/src/cognition/faculties/executive.engine/index.ts +6 -0
  54. package/src/cognition/faculties/executive.engine/messages.ts +102 -0
  55. package/src/cognition/faculties/executive.engine/parser.ts +380 -0
  56. package/src/cognition/faculties/executive.engine/prompt.factory.ts +1053 -0
  57. package/src/cognition/faculties/executive.engine/types.ts +323 -0
  58. package/src/cognition/faculties/exteroception.ts +338 -0
  59. package/src/cognition/faculties/forgetting.curve.ts +202 -0
  60. package/src/cognition/faculties/frustration.evaluator.ts +280 -0
  61. package/src/cognition/faculties/goal.manager.ts +1008 -0
  62. package/src/cognition/faculties/inhibition.controller.ts +351 -0
  63. package/src/cognition/faculties/interoception.ts +417 -0
  64. package/src/cognition/faculties/introspection.engine.ts +246 -0
  65. package/src/cognition/faculties/known.entity.tracker.ts +439 -0
  66. package/src/cognition/faculties/loss.evaluator.ts +224 -0
  67. package/src/cognition/faculties/moral.evaluator.ts +437 -0
  68. package/src/cognition/faculties/novelty.detector.ts +254 -0
  69. package/src/cognition/faculties/persona.consolidator.ts +845 -0
  70. package/src/cognition/faculties/planning.engine/engine.ts +859 -0
  71. package/src/cognition/faculties/planning.engine/plan.frontier.ts +83 -0
  72. package/src/cognition/faculties/planning.engine/plan.store.ts +174 -0
  73. package/src/cognition/faculties/planning.engine/plan.supervision.ts +494 -0
  74. package/src/cognition/faculties/planning.engine/types.ts +143 -0
  75. package/src/cognition/faculties/reputation.tracker.ts +294 -0
  76. package/src/cognition/faculties/reward.evaluator.ts +304 -0
  77. package/src/cognition/faculties/self.model.updater.ts +677 -0
  78. package/src/cognition/faculties/semantic.engine/clustering.ts +673 -0
  79. package/src/cognition/faculties/semantic.engine/index.ts +6 -0
  80. package/src/cognition/faculties/semantic.engine/integrator.ts +750 -0
  81. package/src/cognition/faculties/semantic.engine/types.ts +53 -0
  82. package/src/cognition/faculties/sleep.pressure.regulator.ts +262 -0
  83. package/src/cognition/faculties/social.perception.ts +333 -0
  84. package/src/cognition/faculties/spaced.repetition.ts +605 -0
  85. package/src/cognition/faculties/stress.regulator.ts +375 -0
  86. package/src/cognition/faculties/task.switcher.ts +239 -0
  87. package/src/cognition/faculties/theory.of.mind.ts +333 -0
  88. package/src/cognition/faculties/threat.evaluator.ts +312 -0
  89. package/src/cognition/faculties/working.memory.ts +406 -0
  90. package/src/cognition/generative.model.ts +344 -0
  91. package/src/cognition/heartbeat.ts +70 -0
  92. package/src/cognition/index.ts +291 -0
  93. package/src/cognition/instruction.handler.ts +146 -0
  94. package/src/cognition/memory/index.ts +35 -0
  95. package/src/cognition/memory/vector.adapter.ts +316 -0
  96. package/src/cognition/memory/vector.content.ts +47 -0
  97. package/src/cognition/memory/vector.embedder.ts +212 -0
  98. package/src/cognition/memory/vector.index.ts +459 -0
  99. package/src/cognition/memory/vector.types.ts +65 -0
  100. package/src/cognition/orchestrator.ts +142 -0
  101. package/src/cognition/persona.prior.ts +265 -0
  102. package/src/cognition/schema.registry.ts +145 -0
  103. package/src/cognition/senses/audition.engine/engine.ts +976 -0
  104. package/src/cognition/senses/audition.engine/salience.ts +59 -0
  105. package/src/cognition/senses/base.sense.engine.ts +117 -0
  106. package/src/cognition/senses/gustation.engine.ts +24 -0
  107. package/src/cognition/senses/index.ts +172 -0
  108. package/src/cognition/senses/olfaction.engine.ts +24 -0
  109. package/src/cognition/senses/somatosensation.engine.ts +23 -0
  110. package/src/cognition/senses/vision.engine.ts +23 -0
  111. package/src/cognition/types.ts +63 -0
  112. package/src/cognition/utilities/token.tracker.ts +492 -0
  113. package/src/core/abstracts.ts +60 -0
  114. package/src/core/async.engine.ts +539 -0
  115. package/src/core/clock.ts +196 -0
  116. package/src/core/completion.recorder.ts +122 -0
  117. package/src/core/conflict.detector.ts +140 -0
  118. package/src/core/distributed.ts +626 -0
  119. package/src/core/event.bus.ts +292 -0
  120. package/src/core/inbound.recorder.ts +100 -0
  121. package/src/core/index.ts +167 -0
  122. package/src/core/logger.ts +85 -0
  123. package/src/core/metrics.ts +107 -0
  124. package/src/core/orchestrator.ts +625 -0
  125. package/src/core/replay.ts +589 -0
  126. package/src/core/scenario.ts +120 -0
  127. package/src/core/serialization.ts +448 -0
  128. package/src/core/simulation.ts +197 -0
  129. package/src/core/snapshot.manager.ts +293 -0
  130. package/src/core/state.manager.ts +283 -0
  131. package/src/core/types.ts +274 -0
  132. package/src/core/utils.ts +82 -0
  133. package/src/core/wall.clock.ts +23 -0
  134. package/src/extensions/livestream.ext.ts +570 -0
  135. package/src/extensions/time.ext.ts +339 -0
  136. package/src/index.ts +91 -0
  137. package/src/llm/gate.ts +140 -0
  138. package/src/llm/index.ts +716 -0
  139. package/src/llm/summarizer.ts +170 -0
  140. package/src/llm/wire.contracts.ts +67 -0
  141. package/src/pma/eval.ts +651 -0
  142. package/src/pma/index.ts +1082 -0
  143. package/src/profiles/built-in.ts +7 -0
  144. package/src/profiles/companion.ts +28 -0
  145. package/src/profiles/company-brain.ts +54 -0
  146. package/src/profiles/customer-service.ts +30 -0
  147. package/src/profiles/game-npc.ts +20 -0
  148. package/src/profiles/index.ts +42 -0
  149. package/src/profiles/smart-home.ts +34 -0
  150. package/src/runners/coherence.runner.ts +49 -0
  151. package/src/runners/outreach.runner.ts +158 -0
  152. package/src/runners/social.runner.ts +182 -0
  153. package/src/runners/thin-shim.runner.ts +195 -0
  154. package/src/sdk/will.ts +334 -0
  155. package/src/stem/assembly.audit.ts +88 -0
  156. package/src/stem/distribution.ts +203 -0
  157. package/src/stem/guards/identity.coherence.ts +152 -0
  158. package/src/stem/guards/identity.guard.ts +227 -0
  159. package/src/stem/index.ts +1145 -0
  160. package/src/stem/mind.ts +1090 -0
  161. package/src/stem/tracts/ack.reconciler.ts +55 -0
  162. package/src/stem/tracts/biography.writer.ts +190 -0
  163. package/src/stem/tracts/effector.controller.ts +143 -0
  164. package/src/stem/tracts/health.reporter.ts +113 -0
  165. package/src/stem/tracts/inbound.queue.ts +54 -0
  166. package/src/stem/tracts/outbox.controller.ts +167 -0
  167. package/src/stem/tracts/outbox.writer.ts +133 -0
  168. package/src/stem/tracts/pma.controller.ts +109 -0
  169. package/src/stem/tracts/replay.controller.ts +177 -0
  170. package/src/stem/tracts/sensory.controller.ts +168 -0
  171. package/src/stem/tracts/session.logger.ts +157 -0
  172. package/src/stem/tracts/transport/index.ts +38 -0
  173. package/src/stem/tracts/transport/loopback.transport.ts +86 -0
  174. package/src/stem/tracts/transport/socketio.transport.ts +200 -0
  175. package/src/stem/tracts/transport/stream.transport.ts +152 -0
  176. package/src/stem/tracts/transport/types.ts +193 -0
  177. package/src/stem/tracts/transport.controller.ts +380 -0
  178. package/src/types.ts +116 -0
@@ -0,0 +1,380 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // src/cognition/faculties/executive.engine/parser.ts
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import { logger } from '#core/logger'
6
+ import { REPLY_TEXT_TAG } from '#llm/wire.contracts'
7
+ import type { ReadonlySimulationState } from '#core/types'
8
+ import type { ExecutiveOutputFull, ExecutiveOutputMinimal, IdeationCandidate, IdeationOutput } from '#faculties/executive.engine/types'
9
+
10
+ /**
11
+ * Parse the LLM's text response into an ExecutiveOutputFull.
12
+ * Handles three paths:
13
+ * 1. Direct JSON parse (when LLM outputs valid JSON)
14
+ * 2. Balanced-bracket extraction (when JSON has unescaped content in strings)
15
+ * 3. Heuristic fallback (when parsing fails entirely)
16
+ */
17
+ export function parseResponse(
18
+ responseText: string,
19
+ state: ReadonlySimulationState,
20
+ recentActionTypes: string[]
21
+ ): ExecutiveOutputFull {
22
+ // Find the first code block that contains "actions" (skips tool-query preamble)
23
+ const
24
+ codeBlocks = [ ...responseText.matchAll(/```(?:json)?\s*\n?([\s\S]*?)\n?```/g) ],
25
+ actionsBlock = codeBlocks.find( m => m[1]!.includes('"actions"') ),
26
+ fullText = actionsBlock?.[1]?.trim() ?? responseText.trim()
27
+
28
+ let
29
+ actions: Array<{ type: string; reasoning: string; expectedOutcome: string }>,
30
+ confidence = 0.5
31
+
32
+ // Strategy 1: direct JSON.parse
33
+ try {
34
+ const parsed = JSON.parse( fullText ) as {
35
+ actions: typeof actions
36
+ reasoning?: string
37
+ confidence?: number
38
+ }
39
+ if( !Array.isArray( parsed.actions ) )
40
+ throw new Error('actions is not an array')
41
+
42
+ actions = parsed.actions
43
+ confidence = parsed.confidence ?? 0.5
44
+ }
45
+ catch {
46
+ // Strategy 2: balanced-bracket extractor
47
+ const actionsStr = extractBalancedArray( fullText, 'actions' )
48
+ if( !actionsStr) {
49
+ logger.warn('[executive] No actions found in response — using fallback')
50
+ return buildFallbackOutput( state, recentActionTypes )
51
+ }
52
+
53
+ try { actions = JSON.parse( actionsStr ) }
54
+ catch {
55
+ logger.warn('[executive] Failed to parse actions array — using fallback')
56
+ return buildFallbackOutput( state, recentActionTypes )
57
+ }
58
+
59
+ const confidenceMatch = fullText.match(/"confidence"\s*:\s*([\d.]+)/)
60
+ confidence = confidenceMatch ? parseFloat( confidenceMatch[1]! ) : 0.5
61
+ }
62
+
63
+ // Parse tagged blocks from the extracted JSON text
64
+ const full = parseTaggedBlocks({ actions, reasoning: fullText, confidence }, state )
65
+
66
+ // [REPLY_TEXT] is a plain-text block that lives OUTSIDE the JSON code block.
67
+ // Search the full response text so we find it even when the LLM used a code fence.
68
+ const replyText = extractTextBlock( responseText, REPLY_TEXT_TAG )
69
+ if( replyText ) full.replyText = replyText
70
+
71
+ return full
72
+ }
73
+
74
+ /**
75
+ * Parse the ideation (propose) pass into an IdeationOutput. Mirrors parseResponse's
76
+ * resilience: prefer a fenced ```json block, fall back to raw text, then to
77
+ * balanced-bracket extraction of the `candidates` array. Returns an EMPTY candidate set
78
+ * on total failure — the caller then proceeds as if no options were generated (the
79
+ * decision pass simply runs without an injected set), so a flaky propose pass degrades
80
+ * gracefully rather than breaking the tick. Pure: no state, no I/O.
81
+ */
82
+ export function parseIdeation( responseText: string ): IdeationOutput {
83
+ const
84
+ codeBlocks = [ ...responseText.matchAll(/```(?:json)?\s*\n?([\s\S]*?)\n?```/g) ],
85
+ block = codeBlocks.find( m => m[1]!.includes('"candidates"') ),
86
+ text = block?.[1]?.trim() ?? responseText.trim()
87
+
88
+ const coerce = ( raw: unknown ): IdeationCandidate[] => {
89
+ if( !Array.isArray( raw ) ) return []
90
+ return raw
91
+ .map( c => {
92
+ const o = ( c ?? {} ) as Record<string, unknown>
93
+ return {
94
+ approach: String( o.approach ?? '' ).trim(),
95
+ description: String( o.description ?? '' ).trim(),
96
+ upside: String( o.upside ?? '' ).trim(),
97
+ risk: String( o.risk ?? '' ).trim(),
98
+ }
99
+ } )
100
+ .filter( c => c.approach.length > 0 || c.description.length > 0 )
101
+ }
102
+
103
+ // Strategy 1: direct JSON parse of the (block-or-raw) text.
104
+ try {
105
+ const parsed = JSON.parse( text ) as { candidates?: unknown }
106
+ const candidates = coerce( parsed.candidates )
107
+ if( candidates.length > 0 ) return { candidates }
108
+ }
109
+ catch { /* fall through to bracket extraction */ }
110
+
111
+ // Strategy 2: balanced-bracket extraction of the `candidates` array.
112
+ const arrStr = extractBalancedArray( text, 'candidates' )
113
+ if( arrStr ){
114
+ try { return { candidates: coerce( JSON.parse( arrStr ) ) } }
115
+ catch { /* fall through to empty */ }
116
+ }
117
+
118
+ logger.warn('[executive] ideation parse failed — no candidates (deliberate pass proceeds without an injected set)')
119
+ return { candidates: [] }
120
+ }
121
+
122
+ /**
123
+ * Extract the JSON array value for `key` from `text` using balanced-bracket
124
+ * counting. Properly skips characters inside JSON string literals.
125
+ */
126
+ function extractBalancedArray( text: string, key: string ): string | null {
127
+ const keyMatch = text.match( new RegExp(`"${key}"\\s*:\\s*(\\[)`) )
128
+ if( !keyMatch || keyMatch.index === undefined ) return null
129
+
130
+ const start = keyMatch.index + keyMatch[0].length - 1
131
+ let
132
+ depth = 0,
133
+ inString = false,
134
+ escape = false
135
+
136
+ for( let i = start; i < text.length; i++ ){
137
+ const ch = text[i]!
138
+ if( escape ){
139
+ escape = false
140
+ continue
141
+ }
142
+
143
+ if( ch === '\\' && inString ){
144
+ escape = true
145
+ continue
146
+ }
147
+
148
+ if( ch === '"' ){
149
+ inString = !inString
150
+ continue
151
+ }
152
+
153
+ if( inString ) continue
154
+
155
+ if( ch === '[' || ch === '{' ) depth++
156
+ else if( ch === ']' || ch === '}' ){
157
+ depth--
158
+
159
+ if( depth === 0 )
160
+ return text.slice( start, i + 1 )
161
+ }
162
+ }
163
+
164
+ return null
165
+ }
166
+
167
+ /**
168
+ * Parse tagged JSON blocks from the reasoning text.
169
+ * The LLM embeds optional outputs as [TAG]...[/TAG] blocks.
170
+ */
171
+ function parseTaggedBlocks(
172
+ minimal: ExecutiveOutputMinimal,
173
+ state: ReadonlySimulationState,
174
+ ): ExecutiveOutputFull {
175
+ const
176
+ full: ExecutiveOutputFull = {
177
+ actions: minimal.actions,
178
+ reasoning: minimal.reasoning,
179
+ confidence: minimal.confidence,
180
+ },
181
+ text = minimal.reasoning,
182
+
183
+ // Diagnostic logging — REPLY_TEXT is detected in parseResponse() directly
184
+ taggedTypes = [
185
+ 'PLANS',
186
+ 'BELIEFS',
187
+ 'INTROSPECTION',
188
+ 'NARRATIVE',
189
+ 'IDENTITY',
190
+ 'KNOWN_ENTITIES',
191
+ 'GOALS_NEW',
192
+ 'GOALS_ABANDON',
193
+ 'GOALS_REPRIORITIZE',
194
+ 'EFFECTORS',
195
+ 'SELF_OBS',
196
+ ],
197
+ found = taggedTypes.filter( t => text.includes(`[${t}]`) )
198
+ if( found.length > 0 ){
199
+ const closed = found.map( t => `${t}: ${text.includes(`[/${t}]`) ? 'CLOSED' : 'UNCLOSED'}` ).join(', ')
200
+ logger.info(`[executive] TAGGED BLOCKS: ${closed}`)
201
+ }
202
+
203
+ const parseJsonBlock = ( tag: string ): unknown | null => {
204
+ const block = extractBlock( text, tag )
205
+ if( !block ) return null
206
+
207
+ const trimmed = block.trim()
208
+
209
+ // Parse-then-repair (FN10). The common case is already-valid, correctly
210
+ // escaped JSON — try it verbatim first. The old code *unconditionally*
211
+ // ran a destructive un-escape (\" → ", \\n → literal newline, \\ → \)
212
+ // before parsing, which mangled valid JSON so JSON.parse threw and the
213
+ // block was silently dropped. Whether it broke depended on whether the
214
+ // content happened to contain escapable characters → non-deterministic,
215
+ // content-dependent loss of PLANS/BELIEFS/GOALS/NARRATIVE/… blocks.
216
+ try { return JSON.parse( trimmed ) }
217
+ catch { /* not directly parseable — fall through to best-effort repair */ }
218
+
219
+ // Repair pass only for the rarer case where the model double-escaped the
220
+ // payload. Best-effort: if it still doesn't parse, drop the optional block.
221
+ try {
222
+ const repaired = trimmed.replace(/\\"/g, '"')
223
+ .replace(/\\n/g, '\n')
224
+ .replace(/\\\\/g, '\\')
225
+ return JSON.parse( repaired )
226
+ }
227
+ catch { return null }
228
+ }
229
+
230
+ // Plans
231
+ try {
232
+ const plansData = parseJsonBlock('PLANS') as { plans?: ExecutiveOutputFull['plans'] } | null
233
+ if( plansData?.plans ) full.plans = plansData.plans
234
+ }
235
+ catch { /* ignore parse errors for optional blocks */ }
236
+
237
+ // Beliefs
238
+ try {
239
+ const beliefsData = parseJsonBlock('BELIEFS') as { newBeliefs?: ExecutiveOutputFull['newBeliefs'] } | null
240
+ if( beliefsData?.newBeliefs ) full.newBeliefs = beliefsData.newBeliefs
241
+ }
242
+ catch { /* ignore */ }
243
+
244
+ // Introspection
245
+ try {
246
+ const introspectionData = parseJsonBlock('INTROSPECTION') as { introspection?: ExecutiveOutputFull['introspection'] } | null
247
+ if( introspectionData?.introspection ) full.introspection = introspectionData.introspection
248
+ }
249
+ catch { /* ignore */ }
250
+
251
+ // Narrative
252
+ try {
253
+ const narrativeData = parseJsonBlock('NARRATIVE') as {
254
+ narrative?: string; narrativeThemes?: string[]; currentSelfView?: string
255
+ } | null
256
+ if( narrativeData ){
257
+ if( narrativeData.narrative ) full.narrative = narrativeData.narrative
258
+ if( narrativeData.narrativeThemes ) full.narrativeThemes = narrativeData.narrativeThemes
259
+ if( narrativeData.currentSelfView ) full.currentSelfView = narrativeData.currentSelfView
260
+ }
261
+ }
262
+ catch { /* ignore */ }
263
+
264
+ // Identity updates
265
+ try {
266
+ const identityData = parseJsonBlock('IDENTITY') as { identityUpdates?: ExecutiveOutputFull['identityUpdates'] } | null
267
+ if( identityData?.identityUpdates ) full.identityUpdates = identityData.identityUpdates
268
+
269
+ const knownEntityData = parseJsonBlock('KNOWN_ENTITIES') as { knownEntityUpdates?: ExecutiveOutputFull['knownEntityUpdates'] } | null
270
+ if( knownEntityData?.knownEntityUpdates ) full.knownEntityUpdates = knownEntityData.knownEntityUpdates
271
+ }
272
+ catch { /* ignore */ }
273
+
274
+ // New goals
275
+ try {
276
+ const goalsNewData = parseJsonBlock('GOALS_NEW') as { newGoals?: ExecutiveOutputFull['newGoals'] } | null
277
+ if( goalsNewData?.newGoals ) full.newGoals = goalsNewData.newGoals
278
+ }
279
+ catch { /* ignore */ }
280
+
281
+ // Goals to abandon
282
+ try {
283
+ const goalsAbandonData = parseJsonBlock('GOALS_ABANDON') as { goalsToAbandon?: ExecutiveOutputFull['goalsToAbandon'] } | null
284
+ if( goalsAbandonData?.goalsToAbandon ) full.goalsToAbandon = goalsAbandonData.goalsToAbandon
285
+ }
286
+ catch { /* ignore */ }
287
+
288
+ // Goals to reprioritize
289
+ try {
290
+ const goalsReprioritizeData = parseJsonBlock('GOALS_REPRIORITIZE') as { goalsToReprioritize?: ExecutiveOutputFull['goalsToReprioritize'] } | null
291
+ if( goalsReprioritizeData?.goalsToReprioritize ) full.goalsToReprioritize = goalsReprioritizeData.goalsToReprioritize
292
+ }
293
+ catch { /* ignore */ }
294
+
295
+ // Self-observations
296
+ try {
297
+ const selfObsData = parseJsonBlock('SELF_OBS') as { selfObservations?: string[] } | null
298
+ if( selfObsData?.selfObservations ) full.selfObservations = selfObsData.selfObservations
299
+ }
300
+ catch { /* ignore */ }
301
+
302
+ // [ACK] and legacy [REPLY] blocks are no longer emitted by any engine.
303
+ // [ACK] — removed with the legacy master communication path (Phase 13.8).
304
+ // [REPLY] — replaced by [REPLY_TEXT] plain-text block in conversation facets.
305
+ // Both are preserved as @deprecated fields on ExecutiveOutputFull for compatibility.
306
+
307
+ return full
308
+ }
309
+
310
+ function extractBlock( text: string, tag: string ): string | null {
311
+ const regex = new RegExp(`\\[${tag}\\]\\s*\\n?([\\s\\S]*?)\\n?\\[/${tag}\\]`, 'i')
312
+ const match = text.match( regex )
313
+
314
+ return match?.[1]?.trim() ?? null
315
+ }
316
+
317
+ /**
318
+ * Extract plain text (not JSON) between [TAG] and [/TAG] markers.
319
+ * Used for [REPLY_TEXT] — content is streamed directly to the client,
320
+ * so it must be clean prose, not a JSON payload.
321
+ */
322
+ function extractTextBlock( text: string, tag: string ): string | null {
323
+ const open = `[${tag}]`
324
+ const close = `[/${tag}]`
325
+ const openIdx = text.indexOf( open )
326
+ if( openIdx === -1 ) return null
327
+ const contentStart = openIdx + open.length
328
+ const closeIdx = text.indexOf( close, contentStart )
329
+ const content = closeIdx !== -1
330
+ ? text.slice( contentStart, closeIdx )
331
+ : text.slice( contentStart ) // unclosed block — take everything after the open marker
332
+
333
+ return content.trim() || null
334
+ }
335
+
336
+ /**
337
+ * Build a heuristic fallback output when LLM response parsing fails.
338
+ * Chooses actions based on current physiological state.
339
+ */
340
+ export function buildFallbackOutput(
341
+ state: ReadonlySimulationState,
342
+ recentActionTypes: string[]
343
+ ): ExecutiveOutputFull {
344
+ const energy = state.metrics.get('energy.level') ?? 100
345
+ const sleepPressure = state.metrics.get('sleep.pressure') ?? 0
346
+ const stressLoad = state.metrics.get('stress.load') ?? 0
347
+
348
+ let actionType = 'observe'
349
+ let actionReason = 'No urgent needs — observing surroundings.'
350
+
351
+ if( energy < 20 ){
352
+ actionType = 'replenish energy'
353
+ actionReason = 'Energy critically low — attempting to rest.'
354
+ }
355
+ else if( sleepPressure > 60 ){
356
+ actionType = 'enter deep rest to reduce fatigue'
357
+ actionReason = 'Sleep pressure high — attempting to sleep.'
358
+ }
359
+ else if( stressLoad > 70 ){
360
+ actionType = 'calm my mind and reduce tension'
361
+ actionReason = 'Stress elevated — attempting to meditate.'
362
+ }
363
+ else {
364
+ // Vary the fallback when low-effort actions have dominated recently
365
+ const monotonous = recentActionTypes.filter( t => t === 'reflect' || t === 'observe' ).length >= 3
366
+ if( monotonous) {
367
+ const alternatives = ['explore', 'learn', 'express_emotion', 'rest']
368
+ const tick = state.tick as number
369
+
370
+ actionType = alternatives[ tick % alternatives.length ]!
371
+ actionReason = 'Breaking monotony — choosing a varied action during LLM fallback.'
372
+ }
373
+ }
374
+
375
+ return {
376
+ actions: [{ type: actionType, reasoning: actionReason, expectedOutcome: 'State improves' }],
377
+ reasoning: `Heuristic fallback — LLM unavailable. ${actionReason}`,
378
+ confidence: 0.4
379
+ }
380
+ }