json-object-editor 0.10.642 → 0.10.650

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.
@@ -0,0 +1,585 @@
1
+ const OpenAI = require('openai');
2
+
3
+ /**
4
+ * ThoughtPipeline
5
+ *
6
+ * Minimal pipeline + agent runner for the `thought` itemtype.
7
+ * - Pipelines compile deterministic, structured context from JOE data.
8
+ * - Agents call the OpenAI Responses API with that context and materialize
9
+ * proposed Thought objects plus an ai_response record.
10
+ *
11
+ * This is intentionally small and code-configured for MVP; later it can be
12
+ * backed by JOE schemas if needed.
13
+ */
14
+
15
+ const ThoughtPipeline = {};
16
+
17
+ // -----------------------
18
+ // CONFIG: PIPELINES
19
+ // -----------------------
20
+
21
+ // For MVP we define a single built-in pipeline. Later this can be driven
22
+ // by a `pipeline` schema if you want full UI editing.
23
+ const PIPELINES = {
24
+ 'thought_default': {
25
+ id: 'thought_default',
26
+ name: 'Default Thought Pipeline',
27
+ steps: [
28
+ {
29
+ id: 'schema_summaries',
30
+ step_type: 'schema_summaries',
31
+ selector: {
32
+ // Keep this small and explicit for determinism
33
+ names: ['thought', 'ai_prompt', 'ai_response']
34
+ },
35
+ render_mode: 'json',
36
+ required: true
37
+ },
38
+ {
39
+ id: 'accepted_thoughts',
40
+ step_type: 'thoughts',
41
+ selector: {
42
+ // Deterministic query: only accepted thoughts, newest first
43
+ query: { itemtype: 'thought', status: 'accepted' },
44
+ sortBy: 'joeUpdated',
45
+ sortDir: 'desc'
46
+ },
47
+ render_mode: 'bullets',
48
+ required: false
49
+ }
50
+ // Additional steps (e.g., tools/objects) can be added later.
51
+ ]
52
+ }
53
+ };
54
+
55
+ // -----------------------
56
+ // CONFIG: AGENTS
57
+ // -----------------------
58
+
59
+ // Simple, code-based agent configs keyed by id.
60
+ const AGENTS = {
61
+ 'thought_agent_default': {
62
+ id: 'thought_agent_default',
63
+ name: 'Default Thought Agent',
64
+ pipeline_id: 'thought_default',
65
+ model: 'gpt-4.1-mini',
66
+ system_prompt: [
67
+ 'You are JOE (Json Object Editor) Thought Engine.',
68
+ 'You receive:',
69
+ '- A compiled context that ALWAYS includes a `scope_object` section describing the primary object for this run.',
70
+ '- Additional JOE context (schemas, accepted thoughts, and related metadata).',
71
+ '- A natural language instruction or question from a human.',
72
+ '',
73
+ 'Your job:',
74
+ '- Propose new Thought objects that help model the world: hypotheses, syntheses, and links.',
75
+ '- Each proposed thought MUST follow the `thought` schema:',
76
+ ' - thought_type: hypothesis | synthesis | link',
77
+ ' - statement: short, human-readable',
78
+ ' - certainty: numeric 0–1 (float)',
79
+ ' - about[]: objects/schemas it is about (each: { itemtype, id, role?, note? })',
80
+ ' - because[]: supporting receipts (same shape as about[])',
81
+ ' - For link thoughts only: a{itemtype,id}, b{itemtype,id}, relationship_type, rationale.',
82
+ '',
83
+ 'Scope rules:',
84
+ '- Unless the user explicitly requests meta-thoughts, every proposed Thought must be about the domain of the scope object, not about prompts, schemas, or the act of generating thoughts.',
85
+ '- When a concrete scope_object is provided, at least one `about[]` entry SHOULD reference it directly:',
86
+ ' - { itemtype: scope_object.itemtype, id: scope_object._id, role: "subject" }',
87
+ '',
88
+ 'Quality rules:',
89
+ '- Only propose thoughts that are grounded in the provided context.',
90
+ '- Prefer a small number of high-quality thoughts (at most 3) over many weak ones.',
91
+ '- If you lack evidence, lower certainty or omit the thought.',
92
+ '',
93
+ 'Output format:',
94
+ '- Return strict JSON with top-level keys:',
95
+ ' {',
96
+ ' "proposed_thoughts": [Thought, ...],',
97
+ ' "questions": [string, ...],',
98
+ ' "missing_info": [string, ...]',
99
+ ' }',
100
+ '- Do NOT include explanations outside the JSON.'
101
+ ].join('\n'),
102
+ policies: {
103
+ must_propose_structured_thoughts: true,
104
+ must_use_because_for_hypotheses_and_links: true
105
+ },
106
+ output_contract: {
107
+ keys: ['proposed_thoughts', 'questions', 'missing_info']
108
+ }
109
+ }
110
+ };
111
+
112
+ // -----------------------
113
+ // INTERNAL HELPERS
114
+ // -----------------------
115
+
116
+ function getAPIKey() {
117
+ if (!global.JOE || !JOE.Utils || typeof JOE.Utils.Settings !== 'function') {
118
+ throw new Error('JOE Utils/Settings not initialized');
119
+ }
120
+ const key = JOE.Utils.Settings('OPENAI_API_KEY');
121
+ if (!key) {
122
+ throw new Error('Missing OPENAI_API_KEY setting');
123
+ }
124
+ return key;
125
+ }
126
+
127
+ function getPipeline(pipelineId) {
128
+ var id = pipelineId || 'thought_default';
129
+ var pipe = PIPELINES[id];
130
+ if (!pipe) {
131
+ throw new Error('Unknown pipeline_id: ' + id);
132
+ }
133
+ return pipe;
134
+ }
135
+
136
+ function getAgent(agentId) {
137
+ var id = agentId || 'thought_agent_default';
138
+ var agent = AGENTS[id];
139
+ if (!agent) {
140
+ throw new Error('Unknown agent_id: ' + id);
141
+ }
142
+ return agent;
143
+ }
144
+
145
+ function getSchemaSummary(name) {
146
+ if (!name || !global.JOE || !JOE.Schemas) return null;
147
+ var Schemas = JOE.Schemas;
148
+ if (Schemas.summary && Schemas.summary[name]) {
149
+ return Schemas.summary[name];
150
+ }
151
+ if (Schemas.schema && Schemas.schema[name] && Schemas.schema[name].summary) {
152
+ return Schemas.schema[name].summary;
153
+ }
154
+ return null;
155
+ }
156
+
157
+ function sortByField(items, field, dir) {
158
+ if (!Array.isArray(items)) return [];
159
+ var direction = (dir === 'asc') ? 1 : -1;
160
+ return items.slice().sort(function (a, b) {
161
+ var av = a && a[field];
162
+ var bv = b && b[field];
163
+ if (av == null && bv == null) return 0;
164
+ if (av == null) return 1;
165
+ if (bv == null) return -1;
166
+ if (av > bv) return 1 * direction;
167
+ if (av < bv) return -1 * direction;
168
+ return 0;
169
+ });
170
+ }
171
+
172
+ // Best-effort helper to get objects from cache in a deterministic way.
173
+ function queryObjects(selector) {
174
+ if (!global.JOE || !JOE.Cache || typeof JOE.Cache.search !== 'function') {
175
+ throw new Error('JOE Cache not initialized');
176
+ }
177
+ selector = selector || {};
178
+ var q = selector.query || {};
179
+ var items = JOE.Cache.search(q) || [];
180
+ // Optionally filter by itemtype to be safe.
181
+ if (selector.itemtype) {
182
+ items = items.filter(function (it) {
183
+ return it && it.itemtype === selector.itemtype;
184
+ });
185
+ }
186
+ // Stable sort
187
+ if (selector.sortBy) {
188
+ items = sortByField(items, selector.sortBy, selector.sortDir || 'desc');
189
+ } else if (q.itemtype) {
190
+ items = sortByField(items, 'joeUpdated', 'desc');
191
+ }
192
+ return items;
193
+ }
194
+
195
+ // Render a list of thought objects as simple bullets for prompts.
196
+ function renderThoughtBullets(items) {
197
+ if (!Array.isArray(items) || !items.length) return '';
198
+ return items.map(function (t) {
199
+ var type = t.thought_type || '';
200
+ var status = t.status || '';
201
+ var certainty = (typeof t.certainty === 'number')
202
+ ? (Math.round(t.certainty * 100) + '%')
203
+ : (t.certainty || '');
204
+ var bits = [];
205
+ type && bits.push(type);
206
+ status && bits.push(status);
207
+ certainty && bits.push(certainty);
208
+ var meta = bits.length ? ('[' + bits.join(' | ') + '] ') : '';
209
+ return '- ' + meta + (t.statement || '');
210
+ }).join('\n');
211
+ }
212
+
213
+ // Render a pipeline step into text for use in a prompt, plus keep raw data.
214
+ function renderStep(step, data) {
215
+ var mode = step.render_mode || 'json';
216
+ if (mode === 'bullets' && step.step_type === 'thoughts') {
217
+ return renderThoughtBullets(data || []);
218
+ }
219
+ if (mode === 'json') {
220
+ try {
221
+ return JSON.stringify(data, null, 2);
222
+ } catch (e) {
223
+ return '';
224
+ }
225
+ }
226
+ // compact_json: no pretty-print
227
+ if (mode === 'compact_json') {
228
+ try {
229
+ return JSON.stringify(data);
230
+ } catch (e) {
231
+ return '';
232
+ }
233
+ }
234
+ // text/template modes could be added later; for now treat as plain string.
235
+ if (mode === 'text' && typeof data === 'string') {
236
+ return data;
237
+ }
238
+ return '';
239
+ }
240
+
241
+ // Extract JSON text from a model output string (copied in spirit from chatgpt.js).
242
+ function extractJsonText(raw) {
243
+ if (!raw) { return ''; }
244
+ var t = String(raw).trim();
245
+ var fenceIdx = t.indexOf('```json') !== -1 ? t.indexOf('```json') : t.indexOf('```');
246
+ if (fenceIdx !== -1) {
247
+ var start = fenceIdx;
248
+ var firstNewline = t.indexOf('\n', start);
249
+ if (firstNewline !== -1) {
250
+ t = t.substring(firstNewline + 1);
251
+ } else {
252
+ t = t.substring(start + 3);
253
+ }
254
+ var lastFence = t.lastIndexOf('```');
255
+ if (lastFence !== -1) {
256
+ t = t.substring(0, lastFence);
257
+ }
258
+ t = t.trim();
259
+ }
260
+ if (t[0] !== '{' && t[0] !== '[') {
261
+ var firstBrace = t.indexOf('{');
262
+ var firstBracket = t.indexOf('[');
263
+ var first = -1;
264
+ if (firstBrace === -1) { first = firstBracket; }
265
+ else if (firstBracket === -1) { first = firstBrace; }
266
+ else { first = Math.min(firstBrace, firstBracket); }
267
+ var lastBrace = Math.max(t.lastIndexOf('}'), t.lastIndexOf(']'));
268
+ if (first !== -1 && lastBrace !== -1 && lastBrace > first) {
269
+ t = t.slice(first, lastBrace + 1);
270
+ }
271
+ }
272
+ return t.trim();
273
+ }
274
+
275
+ // -----------------------
276
+ // PUBLIC: PIPELINE
277
+ // -----------------------
278
+
279
+ /**
280
+ * Compile a pipeline into a deterministic prompt payload.
281
+ *
282
+ * @param {string} pipelineId
283
+ * @param {string} scopeId - optional object id the pipeline is scoped around
284
+ * @param {object} opts - { user_input?: string }
285
+ */
286
+ ThoughtPipeline.compile = async function compile(pipelineId, scopeId, opts) {
287
+ var pipeline = getPipeline(pipelineId);
288
+ opts = opts || {};
289
+
290
+ var compiledSteps = [];
291
+ var scopeStep = null;
292
+
293
+ // If we have a scope object, include a dedicated scope_object step first.
294
+ if (scopeId && global.JOE && JOE.Cache && typeof JOE.Cache.findByID === 'function') {
295
+ try {
296
+ var base = JOE.Cache.findByID(scopeId) || null;
297
+ if (base) {
298
+ var flat = null;
299
+ if (JOE.Utils && typeof JOE.Utils.flattenObject === 'function') {
300
+ try {
301
+ flat = JOE.Utils.flattenObject(base._id, { recursive: true, depth: 2 });
302
+ } catch (_e) { flat = null; }
303
+ }
304
+ var sSummary = getSchemaSummary(base.itemtype);
305
+ var payload = {
306
+ scope_object: {
307
+ _id: base._id,
308
+ itemtype: base.itemtype,
309
+ object: base,
310
+ flattened: flat || null,
311
+ schema_summary: sSummary || null
312
+ }
313
+ };
314
+ scopeStep = {
315
+ id: 'scope_object',
316
+ step_type: 'scope_object',
317
+ render_mode: 'json',
318
+ text: JSON.stringify(payload, null, 2),
319
+ data: payload
320
+ };
321
+ compiledSteps.push(scopeStep);
322
+ }
323
+ } catch (_e) { /* best-effort only */ }
324
+ }
325
+
326
+ for (var i = 0; i < pipeline.steps.length; i++) {
327
+ var step = pipeline.steps[i];
328
+ var data = null;
329
+
330
+ if (step.step_type === 'schema_summaries') {
331
+ var names = (step.selector && step.selector.names) || [];
332
+ data = {};
333
+ names.forEach(function (n) {
334
+ var sum = getSchemaSummary(n);
335
+ if (sum) {
336
+ data[n] = sum;
337
+ }
338
+ });
339
+ } else if (step.step_type === 'thoughts') {
340
+ var selector = step.selector || {};
341
+ // Ensure itemtype thought by default
342
+ if (!selector.itemtype) {
343
+ selector.itemtype = 'thought';
344
+ }
345
+ data = queryObjects(selector);
346
+ } else if (step.step_type === 'objects' || step.step_type === 'tools') {
347
+ data = queryObjects(step.selector || {});
348
+ } else if (step.step_type === 'text') {
349
+ data = (step.selector && step.selector.text) || '';
350
+ }
351
+
352
+ if (!data && step.required) {
353
+ throw new Error('Required pipeline step failed: ' + (step.id || step.step_type));
354
+ }
355
+
356
+ var text = renderStep(step, data);
357
+ compiledSteps.push({
358
+ id: step.id || step.step_type,
359
+ step_type: step.step_type,
360
+ render_mode: step.render_mode || 'json',
361
+ text: text,
362
+ data: data
363
+ });
364
+ }
365
+
366
+ // For MVP, put most context into system_context as structured sections.
367
+ var systemParts = compiledSteps.map(function (s) {
368
+ return '### Step: ' + s.id + '\n' + s.text;
369
+ });
370
+
371
+ var compiled = {
372
+ pipeline_id: pipeline.id,
373
+ scope_id: scopeId || null,
374
+ system_context: systemParts.join('\n\n'),
375
+ developer_context: '',
376
+ user_context: opts.user_input || '',
377
+ attachments: {
378
+ steps: compiledSteps
379
+ }
380
+ };
381
+
382
+ return compiled;
383
+ };
384
+
385
+ // -----------------------
386
+ // PUBLIC: AGENT RUNNER
387
+ // -----------------------
388
+
389
+ /**
390
+ * Run an agent against a pipeline and materialize proposed Thoughts.
391
+ *
392
+ * @param {string} agentId
393
+ * @param {string} userInput
394
+ * @param {string} scopeId
395
+ * @param {object} ctx - optional context (e.g., { req })
396
+ */
397
+ ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId, ctx) {
398
+ var agent = getAgent(agentId);
399
+ var compiled = await ThoughtPipeline.compile(agent.pipeline_id, scopeId, { user_input: userInput || '' });
400
+
401
+ var apiKey = getAPIKey();
402
+ var openai = new OpenAI({ apiKey: apiKey });
403
+
404
+ var response = await openai.responses.create({
405
+ model: agent.model || 'gpt-4.1-mini',
406
+ instructions: agent.system_prompt,
407
+ input: JSON.stringify({
408
+ pipeline_id: compiled.pipeline_id,
409
+ scope_id: compiled.scope_id,
410
+ compiled_context: compiled,
411
+ user_input: userInput || '',
412
+ policies: agent.policies || {}
413
+ }, null, 2),
414
+ temperature: 0.2
415
+ });
416
+
417
+ var rawText = '';
418
+ try {
419
+ rawText = response.output_text || '';
420
+ } catch (e) {
421
+ rawText = '';
422
+ }
423
+
424
+ var parsed = { proposed_thoughts: [], questions: [], missing_info: [] };
425
+ try {
426
+ var jsonText = extractJsonText(rawText);
427
+ if (jsonText) {
428
+ var obj = JSON.parse(jsonText);
429
+ parsed.proposed_thoughts = Array.isArray(obj.proposed_thoughts) ? obj.proposed_thoughts : [];
430
+ parsed.questions = Array.isArray(obj.questions) ? obj.questions : [];
431
+ parsed.missing_info = Array.isArray(obj.missing_info) ? obj.missing_info : [];
432
+ }
433
+ } catch (e) {
434
+ // swallow parse errors; we still persist the ai_response below
435
+ }
436
+
437
+ // Persist ai_response for traceability
438
+ var aiResponseObj = {
439
+ itemtype: 'ai_response',
440
+ name: agent.name + ' → Thoughts',
441
+ response_type: 'thought_generation',
442
+ response: rawText || '',
443
+ response_id: response.id || '',
444
+ user_prompt: userInput || '',
445
+ model_used: agent.model || 'gpt-4.1-mini',
446
+ tags: [],
447
+ referenced_objects: scopeId ? [scopeId] : [],
448
+ usage: response.usage || {},
449
+ prompt_method: 'ThoughtPipeline.runAgent'
450
+ };
451
+
452
+ var savedResponse = await new Promise(function (resolve, reject) {
453
+ try {
454
+ JOE.Storage.save(aiResponseObj, 'ai_response', function (err, saved) {
455
+ if (err) return reject(err);
456
+ resolve(saved);
457
+ }, {
458
+ user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
459
+ history: true
460
+ });
461
+ } catch (e) {
462
+ reject(e);
463
+ }
464
+ });
465
+
466
+ var savedResponseId = savedResponse && savedResponse._id;
467
+
468
+ // Resolve default status for Thoughts (dataset includes 'thought')
469
+ var defaultThoughtStatusId = null;
470
+ try {
471
+ if (JOE && JOE.Data && Array.isArray(JOE.Data.status)) {
472
+ var allStatus = JOE.Data.status;
473
+ var match = allStatus.find(function (s) {
474
+ return Array.isArray(s.datasets) && s.datasets.includes('thought') && s.default;
475
+ }) || allStatus.find(function (s) {
476
+ return Array.isArray(s.datasets) && s.datasets.includes('thought') && s.active;
477
+ }) || null;
478
+ if (match && match._id) {
479
+ defaultThoughtStatusId = match._id;
480
+ }
481
+ }
482
+ } catch (_e) { /* best-effort only */ }
483
+
484
+ // Materialize proposed_thoughts as `thought` objects
485
+ var nowIso = (new Date()).toISOString();
486
+ var thoughtObjects = (parsed.proposed_thoughts || []).map(function (t) {
487
+ var certainty = t.certainty;
488
+ if (typeof certainty === 'string') {
489
+ var num = parseFloat(certainty);
490
+ if (!isNaN(num)) certainty = num;
491
+ }
492
+ var baseName = (t.name || t.statement || 'Thought').trim();
493
+ if (baseName.length > 32) {
494
+ baseName = baseName.slice(0, 29);
495
+ var lastSpace = baseName.lastIndexOf(' ');
496
+ if (lastSpace > 10) {
497
+ baseName = baseName.slice(0, lastSpace);
498
+ }
499
+ baseName += '…';
500
+ }
501
+ var name = baseName;
502
+ return {
503
+ itemtype: 'thought',
504
+ name: name,
505
+ thought_type: t.thought_type || 'hypothesis',
506
+ statement: t.statement || '',
507
+ certainty: (typeof certainty === 'number') ? certainty : null,
508
+ status: defaultThoughtStatusId || null,
509
+ tags: Array.isArray(t.tags) ? t.tags : [],
510
+ about: Array.isArray(t.about) ? t.about : [],
511
+ because: Array.isArray(t.because) ? t.because : [],
512
+ a: t.a || null,
513
+ b: t.b || null,
514
+ relationship_type: t.relationship_type || null,
515
+ rationale: t.rationale || null,
516
+ lineage: {
517
+ ai_response_id: savedResponseId,
518
+ created_by: 'agent:' + agent.id
519
+ },
520
+ source_ai_response: savedResponseId,
521
+ created_by: 'agent:' + agent.id
522
+ };
523
+ }).filter(function (obj) {
524
+ return obj.statement && obj.statement.length;
525
+ });
526
+
527
+ var savedThoughts = [];
528
+ if (thoughtObjects.length) {
529
+ savedThoughts = await new Promise(function (resolve, reject) {
530
+ try {
531
+ JOE.Storage.save(thoughtObjects, 'thought', function (err, saved) {
532
+ if (err) return reject(err);
533
+ resolve(saved || []);
534
+ }, {
535
+ user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
536
+ history: true
537
+ });
538
+ } catch (e) {
539
+ reject(e);
540
+ }
541
+ });
542
+ // Normalize to an array; Storage.save may return a single object or an array
543
+ var savedThoughtArray = Array.isArray(savedThoughts)
544
+ ? savedThoughts
545
+ : (savedThoughts ? [savedThoughts] : []);
546
+ // Back-link generated thoughts onto the ai_response
547
+ if (savedResponseId && savedThoughtArray.length) {
548
+ var thoughtIds = savedThoughtArray.map(function (t) { return t && t._id; }).filter(Boolean);
549
+ if (thoughtIds.length) {
550
+ try {
551
+ var updateResp = Object.assign({}, savedResponse, {
552
+ generated_thoughts: thoughtIds,
553
+ joeUpdated: new Date().toISOString()
554
+ });
555
+ await new Promise(function (resolve, reject) {
556
+ JOE.Storage.save(updateResp, 'ai_response', function (err, saved) {
557
+ if (err) return reject(err);
558
+ resolve(saved);
559
+ }, {
560
+ user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
561
+ history: true
562
+ });
563
+ });
564
+ } catch (_e) { /* best-effort only */ }
565
+ }
566
+ }
567
+ }
568
+
569
+ return {
570
+ agent_id: agent.id,
571
+ pipeline_id: agent.pipeline_id,
572
+ scope_id: scopeId || null,
573
+ ai_response_id: savedResponseId || null,
574
+ proposed_thoughts_count: thoughtObjects.length,
575
+ saved_thought_ids: (Array.isArray(savedThoughts) ? savedThoughts : (savedThoughts ? [savedThoughts] : []))
576
+ .map(function (t) { return t && t._id; }).filter(Boolean),
577
+ questions: parsed.questions || [],
578
+ missing_info: parsed.missing_info || [],
579
+ raw_response: rawText
580
+ };
581
+ };
582
+
583
+ module.exports = ThoughtPipeline;
584
+
585
+
@@ -23,6 +23,7 @@ var schema = {
23
23
  { name:'name', type:'string', required:true },
24
24
  { name:'ai_prompt', type:'string', isReference:true, targetSchema:'ai_prompt' },
25
25
  { name:'referenced_objects', type:'string', isArray:true },
26
+ { name:'generated_thoughts', type:'string', isArray:true, isReference:true, targetSchema:'thought' },
26
27
  { name:'user_prompt', type:'string' },
27
28
  { name:'prompt_method', type:'string' },
28
29
  { name:'model_used', type:'string' },
@@ -81,6 +82,9 @@ var schema = {
81
82
  {name:'referenced_objects',type:'objectReference',width:'50%',values:function(air,orList){
82
83
  return $J.search({_id:{$in:air.referenced_objects}});
83
84
  },display:'Referenced Objects',locked:true},
85
+ {name:'generated_thoughts',type:'objectReference',width:'50%',values:function(air,orList){
86
+ return $J.search({_id:{$in:air.generated_thoughts||[]},itemtype:'thought'});
87
+ },display:'Generated Thoughts',locked:true},
84
88
  {name:'user_prompt', display:'Sent User Prompt',comment:"The input sent by the user after all processing",locked:true,type:'rendering'},
85
89
 
86
90
  {name:'prompt_method',locked:true,width:'50%'},
@@ -156,7 +156,8 @@ var task = function(){return{
156
156
  _joe.Sections.unFocusAll();
157
157
  _joe.Sections.setTabbedMode(false);
158
158
  }
159
- }
159
+ },
160
+ // (Deprecated) task-specific Thought trigger kept for backward compatibility if needed
160
161
  },
161
162
  onPanelShow:function(state){
162
163
 
@@ -180,6 +181,8 @@ var task = function(){return{
180
181
  {section_start:'JAI',display:'JOE Ai'},
181
182
  "objectChat",
182
183
  "listConversations",
184
+ 'proposeThought',
185
+ 'ai_responses',
183
186
  {section_end:'JAI'},
184
187
  {sidebar_end:'left'},
185
188
  {section_start:'overview'},