json-object-editor 0.10.642 → 0.10.653
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.
- package/CHANGELOG.md +22 -0
- package/_www/mcp-test.html +18 -0
- package/css/joe-styles.css +2 -1
- package/css/joe.css +4 -2
- package/css/joe.min.css +1 -1
- package/docs/Thought_Concept_for_JOE.md +137 -0
- package/docs/joe_agent_custom_gpt_instructions_v_3.md +57 -3
- package/img/joe-sprite.ai +1155 -758
- package/js/JsonObjectEditor.jquery.craydent.js +185 -5
- package/js/joe-ai.js +57 -0
- package/js/joe.js +186 -6
- package/js/joe.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +19 -0
- package/server/apps/aihub.js +2 -0
- package/server/fields/core.js +49 -0
- package/server/modules/MCP.js +1237 -847
- package/server/modules/ThoughtPipeline.js +599 -0
- package/server/plugins/chatgpt.js +76 -7
- package/server/schemas/ai_pipeline.js +90 -0
- package/server/schemas/ai_response.js +165 -16
- package/server/schemas/task.js +4 -1
- package/server/schemas/thought.js +332 -0
- package/server/webconfig.js +1 -1
|
@@ -0,0 +1,599 @@
|
|
|
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, model })
|
|
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 overrideModel = ctx && ctx.model;
|
|
405
|
+
var modelToUse = overrideModel || agent.model || 'gpt-4.1-mini';
|
|
406
|
+
|
|
407
|
+
var response = await openai.responses.create({
|
|
408
|
+
model: modelToUse,
|
|
409
|
+
instructions: agent.system_prompt,
|
|
410
|
+
input: JSON.stringify({
|
|
411
|
+
pipeline_id: compiled.pipeline_id,
|
|
412
|
+
scope_id: compiled.scope_id,
|
|
413
|
+
compiled_context: compiled,
|
|
414
|
+
user_input: userInput || '',
|
|
415
|
+
policies: agent.policies || {}
|
|
416
|
+
}, null, 2),
|
|
417
|
+
temperature: 0.2
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
var rawText = '';
|
|
421
|
+
try {
|
|
422
|
+
rawText = response.output_text || '';
|
|
423
|
+
} catch (e) {
|
|
424
|
+
rawText = '';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
var parsed = { proposed_thoughts: [], questions: [], missing_info: [] };
|
|
428
|
+
try {
|
|
429
|
+
var jsonText = extractJsonText(rawText);
|
|
430
|
+
if (jsonText) {
|
|
431
|
+
var obj = JSON.parse(jsonText);
|
|
432
|
+
parsed.proposed_thoughts = Array.isArray(obj.proposed_thoughts) ? obj.proposed_thoughts : [];
|
|
433
|
+
parsed.questions = Array.isArray(obj.questions) ? obj.questions : [];
|
|
434
|
+
parsed.missing_info = Array.isArray(obj.missing_info) ? obj.missing_info : [];
|
|
435
|
+
}
|
|
436
|
+
} catch (e) {
|
|
437
|
+
// swallow parse errors; we still persist the ai_response below
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Persist ai_response for traceability
|
|
441
|
+
var aiResponseObj = {
|
|
442
|
+
itemtype: 'ai_response',
|
|
443
|
+
name: agent.name + ' → Thoughts',
|
|
444
|
+
response_type: 'thought_generation',
|
|
445
|
+
response: rawText || '',
|
|
446
|
+
response_json: (function () {
|
|
447
|
+
try {
|
|
448
|
+
var jt = extractJsonText(rawText);
|
|
449
|
+
if (!jt) return null;
|
|
450
|
+
return JSON.parse(jt);
|
|
451
|
+
} catch (_e) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
})(),
|
|
455
|
+
response_id: response.id || '',
|
|
456
|
+
user_prompt: userInput || '',
|
|
457
|
+
model_used: agent.model || 'gpt-4.1-mini',
|
|
458
|
+
tags: [],
|
|
459
|
+
referenced_objects: scopeId ? [scopeId] : [],
|
|
460
|
+
usage: response.usage || {},
|
|
461
|
+
prompt_method: 'ThoughtPipeline.runAgent'
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
var savedResponse = await new Promise(function (resolve, reject) {
|
|
465
|
+
try {
|
|
466
|
+
JOE.Storage.save(aiResponseObj, 'ai_response', function (err, saved) {
|
|
467
|
+
if (err) return reject(err);
|
|
468
|
+
resolve(saved);
|
|
469
|
+
}, {
|
|
470
|
+
user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
|
|
471
|
+
history: true
|
|
472
|
+
});
|
|
473
|
+
} catch (e) {
|
|
474
|
+
reject(e);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
var savedResponseId = savedResponse && savedResponse._id;
|
|
479
|
+
|
|
480
|
+
// Resolve default status for Thoughts (dataset includes 'thought')
|
|
481
|
+
var defaultThoughtStatusId = null;
|
|
482
|
+
try {
|
|
483
|
+
if (JOE && JOE.Data && Array.isArray(JOE.Data.status)) {
|
|
484
|
+
var allStatus = JOE.Data.status;
|
|
485
|
+
var match = allStatus.find(function (s) {
|
|
486
|
+
return Array.isArray(s.datasets) && s.datasets.includes('thought') && s.default;
|
|
487
|
+
}) || allStatus.find(function (s) {
|
|
488
|
+
return Array.isArray(s.datasets) && s.datasets.includes('thought') && s.active;
|
|
489
|
+
}) || null;
|
|
490
|
+
if (match && match._id) {
|
|
491
|
+
defaultThoughtStatusId = match._id;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
} catch (_e) { /* best-effort only */ }
|
|
495
|
+
|
|
496
|
+
// Materialize proposed_thoughts as `thought` objects
|
|
497
|
+
var nowIso = (new Date()).toISOString();
|
|
498
|
+
var thoughtObjects = (parsed.proposed_thoughts || []).map(function (t) {
|
|
499
|
+
var certainty = t.certainty;
|
|
500
|
+
if (typeof certainty === 'string') {
|
|
501
|
+
var num = parseFloat(certainty);
|
|
502
|
+
if (!isNaN(num)) certainty = num;
|
|
503
|
+
}
|
|
504
|
+
var baseName = (t.name || t.statement || 'Thought').trim();
|
|
505
|
+
if (baseName.length > 32) {
|
|
506
|
+
baseName = baseName.slice(0, 29);
|
|
507
|
+
var lastSpace = baseName.lastIndexOf(' ');
|
|
508
|
+
if (lastSpace > 10) {
|
|
509
|
+
baseName = baseName.slice(0, lastSpace);
|
|
510
|
+
}
|
|
511
|
+
baseName += '…';
|
|
512
|
+
}
|
|
513
|
+
var name = baseName;
|
|
514
|
+
return {
|
|
515
|
+
itemtype: 'thought',
|
|
516
|
+
name: name,
|
|
517
|
+
thought_type: t.thought_type || 'hypothesis',
|
|
518
|
+
statement: t.statement || '',
|
|
519
|
+
certainty: (typeof certainty === 'number') ? certainty : null,
|
|
520
|
+
status: defaultThoughtStatusId || null,
|
|
521
|
+
tags: Array.isArray(t.tags) ? t.tags : [],
|
|
522
|
+
about: Array.isArray(t.about) ? t.about : [],
|
|
523
|
+
because: Array.isArray(t.because) ? t.because : [],
|
|
524
|
+
a: t.a || null,
|
|
525
|
+
b: t.b || null,
|
|
526
|
+
relationship_type: t.relationship_type || null,
|
|
527
|
+
rationale: t.rationale || null,
|
|
528
|
+
lineage: {
|
|
529
|
+
ai_response_id: savedResponseId,
|
|
530
|
+
created_by: 'agent:' + agent.id
|
|
531
|
+
},
|
|
532
|
+
source_ai_response: savedResponseId,
|
|
533
|
+
created_by: 'agent:' + agent.id,
|
|
534
|
+
creator_type: 'agent',
|
|
535
|
+
creator_id: agent.id
|
|
536
|
+
};
|
|
537
|
+
}).filter(function (obj) {
|
|
538
|
+
return obj.statement && obj.statement.length;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
var savedThoughts = [];
|
|
542
|
+
if (thoughtObjects.length) {
|
|
543
|
+
savedThoughts = await new Promise(function (resolve, reject) {
|
|
544
|
+
try {
|
|
545
|
+
JOE.Storage.save(thoughtObjects, 'thought', function (err, saved) {
|
|
546
|
+
if (err) return reject(err);
|
|
547
|
+
resolve(saved || []);
|
|
548
|
+
}, {
|
|
549
|
+
user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
|
|
550
|
+
history: true
|
|
551
|
+
});
|
|
552
|
+
} catch (e) {
|
|
553
|
+
reject(e);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
// Normalize to an array; Storage.save may return a single object or an array
|
|
557
|
+
var savedThoughtArray = Array.isArray(savedThoughts)
|
|
558
|
+
? savedThoughts
|
|
559
|
+
: (savedThoughts ? [savedThoughts] : []);
|
|
560
|
+
// Back-link generated thoughts onto the ai_response
|
|
561
|
+
if (savedResponseId && savedThoughtArray.length) {
|
|
562
|
+
var thoughtIds = savedThoughtArray.map(function (t) { return t && t._id; }).filter(Boolean);
|
|
563
|
+
if (thoughtIds.length) {
|
|
564
|
+
try {
|
|
565
|
+
var updateResp = Object.assign({}, savedResponse, {
|
|
566
|
+
generated_thoughts: thoughtIds,
|
|
567
|
+
joeUpdated: new Date().toISOString()
|
|
568
|
+
});
|
|
569
|
+
await new Promise(function (resolve, reject) {
|
|
570
|
+
JOE.Storage.save(updateResp, 'ai_response', function (err, saved) {
|
|
571
|
+
if (err) return reject(err);
|
|
572
|
+
resolve(saved);
|
|
573
|
+
}, {
|
|
574
|
+
user: (ctx && ctx.req && ctx.req.User) || { name: 'thought_agent' },
|
|
575
|
+
history: true
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
} catch (_e) { /* best-effort only */ }
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return {
|
|
584
|
+
agent_id: agent.id,
|
|
585
|
+
pipeline_id: agent.pipeline_id,
|
|
586
|
+
scope_id: scopeId || null,
|
|
587
|
+
ai_response_id: savedResponseId || null,
|
|
588
|
+
proposed_thoughts_count: thoughtObjects.length,
|
|
589
|
+
saved_thought_ids: (Array.isArray(savedThoughts) ? savedThoughts : (savedThoughts ? [savedThoughts] : []))
|
|
590
|
+
.map(function (t) { return t && t._id; }).filter(Boolean),
|
|
591
|
+
questions: parsed.questions || [],
|
|
592
|
+
missing_info: parsed.missing_info || [],
|
|
593
|
+
raw_response: rawText
|
|
594
|
+
};
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
module.exports = ThoughtPipeline;
|
|
598
|
+
|
|
599
|
+
|