json-object-editor 0.10.650 → 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.
@@ -392,7 +392,7 @@ ThoughtPipeline.compile = async function compile(pipelineId, scopeId, opts) {
392
392
  * @param {string} agentId
393
393
  * @param {string} userInput
394
394
  * @param {string} scopeId
395
- * @param {object} ctx - optional context (e.g., { req })
395
+ * @param {object} ctx - optional context (e.g., { req, model })
396
396
  */
397
397
  ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId, ctx) {
398
398
  var agent = getAgent(agentId);
@@ -401,8 +401,11 @@ ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId,
401
401
  var apiKey = getAPIKey();
402
402
  var openai = new OpenAI({ apiKey: apiKey });
403
403
 
404
+ var overrideModel = ctx && ctx.model;
405
+ var modelToUse = overrideModel || agent.model || 'gpt-4.1-mini';
406
+
404
407
  var response = await openai.responses.create({
405
- model: agent.model || 'gpt-4.1-mini',
408
+ model: modelToUse,
406
409
  instructions: agent.system_prompt,
407
410
  input: JSON.stringify({
408
411
  pipeline_id: compiled.pipeline_id,
@@ -440,6 +443,15 @@ ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId,
440
443
  name: agent.name + ' → Thoughts',
441
444
  response_type: 'thought_generation',
442
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
+ })(),
443
455
  response_id: response.id || '',
444
456
  user_prompt: userInput || '',
445
457
  model_used: agent.model || 'gpt-4.1-mini',
@@ -518,7 +530,9 @@ ThoughtPipeline.runAgent = async function runAgent(agentId, userInput, scopeId,
518
530
  created_by: 'agent:' + agent.id
519
531
  },
520
532
  source_ai_response: savedResponseId,
521
- created_by: 'agent:' + agent.id
533
+ created_by: 'agent:' + agent.id,
534
+ creator_type: 'agent',
535
+ creator_id: agent.id
522
536
  };
523
537
  }).filter(function (obj) {
524
538
  return obj.statement && obj.statement.length;
@@ -432,7 +432,40 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
432
432
  model: 'gpt-4o',
433
433
  });
434
434
  coloredLog(chatCompletion);
435
- return {payload,chatCompletion,content:chatCompletion.choices[0].message.content};
435
+ const text = chatCompletion.choices && chatCompletion.choices[0] && chatCompletion.choices[0].message && chatCompletion.choices[0].message.content || '';
436
+ // Optionally persist as ai_response with parsed JSON when applicable
437
+ const parsed = (function(){
438
+ try {
439
+ const jt = extractJsonText(text);
440
+ return jt ? JSON.parse(jt) : null;
441
+ } catch(_e){ return null; }
442
+ })();
443
+ try {
444
+ var creator_type = null;
445
+ var creator_id = null;
446
+ try{
447
+ var u = req && req.User;
448
+ if (u && u._id){
449
+ creator_type = 'user';
450
+ creator_id = u._id;
451
+ }
452
+ }catch(_e){}
453
+ const aiResponse = {
454
+ itemtype: 'ai_response',
455
+ name: 'Test Prompt → ChatGPT',
456
+ response_type: 'testPrompt',
457
+ response: text,
458
+ response_json: parsed,
459
+ response_id: chatCompletion.id || '',
460
+ user_prompt: payload && payload.data && payload.data.prompt || 'Tell me a story about JOE: the json object editor in under 256 chars.',
461
+ model_used: 'gpt-4o',
462
+ created: (new Date()).toISOString(),
463
+ creator_type: creator_type,
464
+ creator_id: creator_id
465
+ };
466
+ JOE.Storage.save(aiResponse, 'ai_response', function(){}, { history: false, user: (req && req.User) || { name:'system' } });
467
+ } catch(_e){ /* best-effort only */ }
468
+ return {payload,chatCompletion,content:text};
436
469
  } catch (error) {
437
470
  if (error.status === 429) {
438
471
  return { errors: 'You exceeded your current quota, please check your plan and billing details.' };
@@ -498,7 +531,7 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
498
531
  response: chatContent,
499
532
  payload,
500
533
  prompt_method:req.params.method
501
- });
534
+ }, req && req.User);
502
535
  coloredLog("response saved -"+responseName);
503
536
  return {payload,
504
537
  businessOBJ,
@@ -549,8 +582,16 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
549
582
  return match ? match[1] : null;
550
583
  }
551
584
 
552
- async function saveAIResponse(data) {
585
+ async function saveAIResponse(data, user) {
553
586
  try {
587
+ var creator_type = null;
588
+ var creator_id = null;
589
+ try{
590
+ if (user && user._id){
591
+ creator_type = 'user';
592
+ creator_id = user._id;
593
+ }
594
+ }catch(_e){}
554
595
  const aiResponse = {
555
596
  name: data.name,
556
597
  itemtype: 'ai_response',
@@ -560,7 +601,9 @@ function shrinkUnderstandObjectMessagesForTokens(messages) {
560
601
  payload: data.payload,
561
602
  prompt_method:data.prompt_method,
562
603
  created: (new Date).toISOString(),
563
- _id:cuid()
604
+ _id:cuid(),
605
+ creator_type: creator_type,
606
+ creator_id: creator_id
564
607
  // Add any other fields you want to save
565
608
  };
566
609
  await new Promise((resolve, reject) => {
@@ -880,7 +923,9 @@ this.executeJOEAiPrompt = async function(data, req, res) {
880
923
  params,
881
924
  referenced_object_ids: referencedObjectIds,
882
925
  response_id:response.id,
883
- usage: response.usage || {}
926
+ usage: response.usage || {},
927
+ user: req && req.User,
928
+ ai_assistant_id: data.ai_assistant_id
884
929
  });
885
930
 
886
931
  return { success: true, ai_response_id: saved._id,response:response.output_text || "",usage:response.usage };
@@ -903,13 +948,34 @@ this.executeJOEAiPrompt = async function(data, req, res) {
903
948
  max_tokens: prompt.max_tokens ?? 1200
904
949
  };
905
950
  }
906
- async function saveAiResponseRefactor({ prompt, ai_response_content, user_prompt, params, referenced_object_ids,response_id,usage}) {
951
+ async function saveAiResponseRefactor({ prompt, ai_response_content, user_prompt, params, referenced_object_ids,response_id,usage,user,ai_assistant_id}) {
907
952
  var response_keys = [];
908
953
  try {
909
954
  response_keys = Object.keys(JSON.parse(ai_response_content));
910
955
  }catch (e) {
911
956
  console.error('❌ Error parsing AI response content for keys:', e);
912
957
  }
958
+ // Best-effort parse into JSON for downstream agents (Thought pipeline, etc.)
959
+ let parsedResponse = null;
960
+ try {
961
+ const jt = extractJsonText(ai_response_content);
962
+ if (jt) {
963
+ parsedResponse = JSON.parse(jt);
964
+ }
965
+ } catch(_e) {
966
+ parsedResponse = null;
967
+ }
968
+ var creator_type = null;
969
+ var creator_id = null;
970
+ try{
971
+ if (ai_assistant_id){
972
+ creator_type = 'ai_assistant';
973
+ creator_id = ai_assistant_id;
974
+ } else if (user && user._id){
975
+ creator_type = 'user';
976
+ creator_id = user._id;
977
+ }
978
+ }catch(_e){}
913
979
  const aiResponse = {
914
980
  name: `${prompt.name}`,
915
981
  itemtype: 'ai_response',
@@ -917,6 +983,7 @@ this.executeJOEAiPrompt = async function(data, req, res) {
917
983
  prompt_name: prompt.name,
918
984
  prompt_method:prompt.prompt_method,
919
985
  response: ai_response_content,
986
+ response_json: parsedResponse,
920
987
  response_keys: response_keys,
921
988
  response_id:response_id||'',
922
989
  user_prompt: user_prompt,
@@ -926,7 +993,9 @@ this.executeJOEAiPrompt = async function(data, req, res) {
926
993
  model_used: prompt.ai_model || "gpt-4o",
927
994
  referenced_objects: referenced_object_ids, // new flexible array of referenced object ids
928
995
  created: (new Date).toISOString(),
929
- _id: cuid()
996
+ _id: cuid(),
997
+ creator_type: creator_type,
998
+ creator_id: creator_id
930
999
  };
931
1000
 
932
1001
  await new Promise((resolve, reject) => {
@@ -0,0 +1,90 @@
1
+ var schema = {
2
+ title: "AI Pipeline | ${name}",
3
+ display: "AI Pipeline",
4
+ info: "Configurable AI context pipelines composed of ordered steps, used to compile context for agents like the Thought Engine.",
5
+ summary: {
6
+ description: "Declarative definition of AI context pipelines: an ordered list of steps (schema summaries, thoughts, objects, etc.) that feed agents.",
7
+ purpose: "Use ai_pipeline to configure what context is compiled for a given agent run (e.g., which schemas, thoughts, and objects are included) without changing code.",
8
+ labelField: "name",
9
+ defaultSort: { field: "created", dir: "desc" },
10
+ searchableFields: ["name", "pipeline_id", "info", "_id"],
11
+ allowedSorts: ["created", "joeUpdated", "name"],
12
+ relationships: {
13
+ outbound: [
14
+ // Future: agents that reference this pipeline_id
15
+ ],
16
+ inbound: { graphRef: "server/relationships.graph.json" }
17
+ },
18
+ joeManagedFields: ["created", "joeUpdated"],
19
+ fields: [
20
+ { name: "_id", type: "string", required: true },
21
+ { name: "itemtype", type: "string", required: true, const: "ai_pipeline" },
22
+ { name: "name", type: "string", required: true },
23
+ { name: "pipeline_id", type: "string", required: true },
24
+ { name: "info", type: "string" },
25
+ // Steps are minimal, but expressive enough to mirror current PIPELINES config
26
+ { name: "steps", type: "objectList" },
27
+ { name: "joeUpdated", type: "string", format: "date-time" },
28
+ { name: "created", type: "string", format: "date-time" }
29
+ ]
30
+ },
31
+ listView: {
32
+ title: function (p) {
33
+ return `
34
+ <joe-subtext>${_joe.Utils.prettyPrintDTS(p.created)}</joe-subtext>
35
+ <joe-title>${p.name}</joe-title>
36
+ <joe-subtitle>${p.pipeline_id || ""}</joe-subtitle>
37
+ `;
38
+ },
39
+ listWindowTitle: "AI Pipelines"
40
+ },
41
+ sorter: ["!created", "name"],
42
+ fields: [
43
+ "name",
44
+ { name: "pipeline_id", width: "50%", comment: "Logical id used by agents and ThoughtPipeline (e.g., thought_default, protocol_planning)." },
45
+ "info",
46
+
47
+ { section_start: "steps", display: "Pipeline Steps", collapsed: false },
48
+ {
49
+ name: "steps",
50
+ type: "objectList",
51
+ display: "Steps",
52
+ comment: "Ordered steps that define how context is compiled.",
53
+ properties: [
54
+ { name: "id", width: "20%", comment: "Stable step id (e.g., schema_summaries, accepted_thoughts)." },
55
+ {
56
+ name: "step_type",
57
+ type: "select",
58
+ values: ["schema_summaries", "thoughts", "objects", "scope_object", "text", "tools"],
59
+ width: "15%",
60
+ comment: "Determines how this step is executed."
61
+ },
62
+ {
63
+ name: "render_mode",
64
+ type: "select",
65
+ values: ["json", "compact_json", "bullets", "text"],
66
+ width: "15%",
67
+ comment: "How this step is rendered into the agent prompt."
68
+ },
69
+ { name: "required", type: "boolean", width: "10%", comment: "If true, pipeline fails when this step cannot be resolved." },
70
+ {
71
+ name: "selector",
72
+ type: "code",
73
+ language: "json",
74
+ width: "40%",
75
+ comment: "Free-form selector JSON (e.g., { \"names\": [\"thought\",\"ai_prompt\" ] } or { \"query\": {\"itemtype\":\"thought\"}, \"sortBy\":\"joeUpdated\" } )."
76
+ }
77
+ ]
78
+ },
79
+ { section_end: "steps" },
80
+
81
+ { section_start: "system", collapsed: true },
82
+ "_id",
83
+ "created",
84
+ "itemtype",
85
+ { section_end: "system" }
86
+ ],
87
+ idprop: "_id"
88
+ };
89
+
90
+ module.exports = schema;
@@ -1,5 +1,6 @@
1
1
  var schema = {
2
2
  title : 'Ai Response | ${name}',
3
+ display:'AI Response',
3
4
  info:"An ai response from openai etc",
4
5
  summary:{
5
6
  description:'Stored AI response objects generated from ai_prompt and the chatgpt plugin.',
@@ -29,9 +30,12 @@ var schema = {
29
30
  { name:'model_used', type:'string' },
30
31
  { name:'response_type', type:'string' },
31
32
  { name:'response', type:'string' },
33
+ { name:'response_json', type:'object' },
32
34
  { name:'response_keys', type:'string', isArray:true },
33
35
  { name:'response_id', type:'string' },
34
36
  { name:'usage', type:'object' },
37
+ // { name:'creator_type', type:'string', enumValues:['user','ai_assistant'] },
38
+ // { name:'creator_id', type:'string' },
35
39
  { name:'tags', type:'string', isArray:true, isReference:true, targetSchema:'tag' },
36
40
  { name:'joeUpdated', type:'string', format:'date-time' },
37
41
  { name:'created', type:'string', format:'date-time' }
@@ -61,18 +65,129 @@ var schema = {
61
65
  },
62
66
  listWindowTitle: 'Ai Responses'
63
67
  },
64
- subsets: function(){
65
- return _joe.Filter.Options.tags({group:true,collapsed:false}).concat(_joe.getDataset('ai_prompt').map(prompt => {
66
- var color = prompt.status && $J.get(prompt.status,'status').color;
67
- return {
68
- name: prompt.name,
69
- id: prompt._id,
70
- filter: { ai_prompt: prompt._id },
71
- stripecolor: color || null // optional
72
- };
73
- }))
74
- },
68
+ subsets: function(a,b,c){
69
+ // Base subsets: tag-based and by ai_prompt
70
+ var subsets = _joe.Filter.Options.tags({group:true,collapsed:false}).concat(
71
+ _joe.getDataset('ai_prompt').map(function(prompt){
72
+ var color = prompt.status && $J.get(prompt.status,'status').color;
73
+ return {
74
+ name: prompt.name,
75
+ id: prompt._id,
76
+ filter: { ai_prompt: prompt._id },
77
+ stripecolor: color || null // optional
78
+ };
79
+ })
80
+ );
75
81
 
82
+ // Additional subset: "Generated Thoughts" – detect from raw JSON
83
+ // We look inside response_json for proposed_thoughts / thoughts arrays.
84
+ try{
85
+ var ds = _joe.getDataset('ai_response') || [];
86
+ var ids = [];
87
+ ds.map(function(r){
88
+ var j = r && r.response_json;
89
+ if (!j) { return; }
90
+ if (Array.isArray(j.proposed_thoughts) && j.proposed_thoughts.length) {
91
+ ids.push(r._id);
92
+ return;
93
+ }
94
+ if (Array.isArray(j.thoughts) && j.thoughts.length) {
95
+ ids.push(r._id);
96
+ }
97
+ });
98
+ if (ids.length) {
99
+ subsets.push({
100
+ name: 'Generated Thoughts',
101
+ id: 'generated_thoughts',
102
+ filter: { _id: { $in: ids } },
103
+ stripecolor: _joe.Colors.ai
104
+ });
105
+ }
106
+ }catch(_e){ /* best-effort only */ }
107
+ // subsets.push(
108
+ // {
109
+ // name: 'True',
110
+ // id: 'isTrue',
111
+ // filter: function(air,index, arra){
112
+ // return air.response_json && air.response_json.proposed_thoughts && air.response_json.proposed_thoughts.length > 0;
113
+ // },
114
+ // stripecolor: 'gold'
115
+ // }
116
+ // );
117
+ return subsets;
118
+ },
119
+ stripeColor:function(air){
120
+ if(air.response_json && air.response_json.proposed_thoughts){
121
+ return {color:_joe.Colors.ai,title:'Proposed '+air.response_json.proposed_thoughts.length+' Thoughts'};
122
+ }
123
+ return null;
124
+ },
125
+ filters:function(){
126
+ var filters = [];
127
+ // Tag filters
128
+ filters = filters.concat(
129
+ _joe.Filter.Options.tags({group:'tags',untagged:true,collapsed:true})
130
+ );
131
+ // creator_type simple value grouping
132
+ // try{
133
+ // filters = filters.concat(
134
+ // _joe.Filter.Options.getDatasetPropertyValues('ai_response','creator_type',{ group: 'creator_type', collapsed: true })
135
+ // );
136
+ // }catch(_e){}
137
+ // // creator_id filters with human-readable names
138
+ // try{
139
+ // var dataset = _joe.getDataset('ai_response') || [];
140
+ // var byCreator = {};
141
+ // dataset.map(function(r){
142
+ // if(!r.creator_id){ return; }
143
+ // var key = r.creator_type+':'+r.creator_id;
144
+ // if(!byCreator[key]){
145
+ // var name = r.creator_id;
146
+ // if(r.creator_type === 'user' && _joe.Data.user){
147
+ // var u = _joe.Data.user.find(function(x){ return x && x._id === r.creator_id; });
148
+ // if(u){ name = u.fullname || u.name || u.email || u._id; }
149
+ // }else if(r.creator_type === 'ai_assistant' && _joe.Data.ai_assistant){
150
+ // var a = _joe.Data.ai_assistant.find(function(x){ return x && x._id === r.creator_id; });
151
+ // if(a){ name = a.name || a._id; }
152
+ // }
153
+ // byCreator[key] = {
154
+ // name: name,
155
+ // filter: { creator_type: r.creator_type, creator_id: r.creator_id }
156
+ // };
157
+ // }
158
+ // });
159
+ // var keys = Object.keys(byCreator);
160
+ // if(keys.length){
161
+ // filters.push({ group_start:'creator', collapsed:true });
162
+ // keys.sort(function(a,b){
163
+ // var na = byCreator[a].name.toLowerCase();
164
+ // var nb = byCreator[b].name.toLowerCase();
165
+ // if(na>nb) return 1;
166
+ // if(na<nb) return -1;
167
+ // return 0;
168
+ // }).map(function(k){
169
+ // filters.push({
170
+ // name: byCreator[k].name,
171
+ // filter: byCreator[k].filter
172
+ // });
173
+ // });
174
+ // filters.push({ group_end:'creator' });
175
+ // }
176
+ // }catch(_e){}
177
+ return filters;
178
+ },
179
+ itemExpander:function(air){
180
+ if(air.response_json && air.response_json.proposed_thoughts){
181
+ //list proposed thoughts in html with header "Proposed Thoughts"
182
+ var html = `<joe-card><joe-title>Proposed Thoughts</joe-title>`;
183
+ air.response_json.proposed_thoughts.map(thought=>{
184
+ html += `<joe-subtitle>${thought.statement}</joe-subtitle><br/>`;
185
+ });
186
+ html += `</joe-card>`;
187
+ return html;
188
+ }
189
+ return _joe.schemas.thought.methods.listThoughts(air);
190
+ },
76
191
  sorter:['!created','name'],
77
192
  checkChanges:false,
78
193
  fields:[
@@ -83,7 +198,7 @@ var schema = {
83
198
  return $J.search({_id:{$in:air.referenced_objects}});
84
199
  },display:'Referenced Objects',locked:true},
85
200
  {name:'generated_thoughts',type:'objectReference',width:'50%',values:function(air,orList){
86
- return $J.search({_id:{$in:air.generated_thoughts||[]},itemtype:'thought'});
201
+ return $J.search({source_ai_response:air._id,itemtype:'thought'});
87
202
  },display:'Generated Thoughts',locked:true},
88
203
  {name:'user_prompt', display:'Sent User Prompt',comment:"The input sent by the user after all processing",locked:true,type:'rendering'},
89
204
 
@@ -98,17 +213,47 @@ var schema = {
98
213
 
99
214
  //{name:'params', type:'rendering', locked:true},
100
215
  {section_start:'response',collapsed:false},
101
- {name:'response_type',type:'text',display:'Response Type',
102
- placeholder:'(a code for the responsetype)',locked:true,width:'50%',condition:function(v){
216
+ {
217
+ name:'response_type',
218
+ type:'text',
219
+ display:'Response Type',
220
+ placeholder:'(a code for the responsetype)',
221
+ locked:true,
222
+ width:'50%',
223
+ condition:function(v){
103
224
  return v.length > 0;
104
- }},
105
-
106
- {name:'response', type:'rendering', locked:true,height:'500px'},
225
+ }
226
+ },
227
+ {name:'response', type:'rendering', locked:true, height:'500px'},
228
+ {
229
+ name:'response_json',
230
+ type:'content',
231
+ display:'Parsed Response (JSON)',
232
+ locked:true,
233
+ run:function(air){
234
+ if (!air || !air.response_json) { return ''; }
235
+ try{
236
+ var json = JSON.stringify(air.response_json, null, 2);
237
+ // basic HTML escape for safety
238
+ json = String(json)
239
+ .replace(/&/g,'&amp;')
240
+ .replace(/</g,'&lt;')
241
+ .replace(/>/g,'&gt;');
242
+ return '<pre style="white-space:pre-wrap; max-height:260px; overflow:auto;">'+json+'</pre>';
243
+ }catch(e){
244
+ return '<pre>'+String(air.response_json)+'</pre>';
245
+ }
246
+ }
247
+ },
107
248
  {name:'response_keys', type:'text', locked:true,display:'Response Keys'},
108
249
  {name:'response_id', type:'text', display:'openAI response ID',locked:true},
109
250
  {section_end:'response'},
110
251
  {sidebar_start:'right', collapsed:false},
252
+ // {section_start:'creator'},
253
+ // 'creator_type','creator_id',
254
+ // {section_end:'creator'},
111
255
  {section_start:'workflow'},
256
+ 'status',
112
257
  'tags',
113
258
  {section_end:'workflow'},
114
259
  {section_start:'tokens'},
@@ -1,6 +1,8 @@
1
+ const { methods } = require("./ai_prompt");
2
+
1
3
  var thought = {
2
4
  display:'Thought',
3
- title: 'Thought | ${thought_type}: ${statement}',
5
+ title: 'Thought | ${name}',
4
6
  info: "Persistent reasoning artifact (hypothesis, synthesis, or link) used to build an auditable world model in JOE.",
5
7
  menuicon:`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
6
8
  <path d="M418.79 152.21c54.1-4.68 106.11 14.11 145.57 50.7 179.88-61.28 357.82 94.91 322.09 281.77-17.7 92.57-93.99 170.46-185.58 191.53-7.83 1.8-34.03 4.23-38 6.47-2.3 1.29-14.64 18.32-18.65 22.27-82.76 81.43-218.9 67.72-286.83-24.62l-35.16 3.37c-205.89-4.36-263.08-280.1-74.73-364.78 10.05-87.72 83.39-159.08 171.29-166.69Zm-39.41 491.05c4.96 4.51 11.22 16.94 16.69 23.33 46.94 54.84 121.44 70.23 186.17 36.84 26.3-13.56 42.96-35.52 62.34-56.84 75.92-4.51 145.19-42.32 182.22-109.5 88.11-159.86-57.37-346.63-233.69-303.07-9.81 2.43-27.09 11.14-35.93 10.11-8.59-1-28.84-22.68-38.58-29.14-86.51-57.47-207.06-11.17-229.88 89.98-3.02 13.37-1.37 36.64-13.02 43.9-16.07 10.02-32.75 13.56-49.51 26.98-116.69 93.45-38.49 282 110.22 268.53 13.11-1.19 31.88-11.21 42.98-1.12Z"/>
@@ -12,7 +14,7 @@ var thought = {
12
14
  purpose: 'Use thoughts to store inspectable reasoning: hypotheses to be vetted, syntheses that compress many objects, and binary links (A↔B) with receipts. Thoughts are reusable as context in pipelines and agent runs.',
13
15
  labelField: 'name',
14
16
  defaultSort: { field: 'joeUpdated', dir: 'desc' },
15
- searchableFields: ['name', 'statement', 'info', 'thought_type', 'status', 'relationship_type', 'tags', '_id'],
17
+ searchableFields: ['name', 'statement', 'info', 'thought_type', 'status', 'relationship_type', 'created_by', 'creator_type', 'creator_id', 'tags', '_id'],
16
18
  allowedSorts: ['joeUpdated', 'created', 'thought_type', 'status', 'certainty'],
17
19
  relationships: {
18
20
  outbound: [
@@ -47,9 +49,11 @@ var thought = {
47
49
  { name: 'b', type: 'object' },
48
50
  { name: 'relationship_type', type: 'string' },
49
51
  { name: 'rationale', type: 'string' },
50
- // Lineage / provenance (ai_response_id, created_by, reviewed_by, etc.)
52
+ // Lineage / provenance (ai_response_id, created_by/creator, reviewed_by, etc.)
51
53
  { name: 'source_ai_response', type: 'string', isReference: true, targetSchema: 'ai_response' },
52
54
  { name: 'created_by', type: 'string' },
55
+ { name: 'creator_type', type: 'string', enumValues: ['user','agent'] },
56
+ { name: 'creator_id', type: 'string' },
53
57
  { name: 'lineage', type: 'object' },
54
58
  { name: 'joeUpdated', type: 'string', format: 'date-time', required: true },
55
59
  { name: 'created', type: 'string', format: 'date-time', required: true }
@@ -68,9 +72,54 @@ var thought = {
68
72
  var filters = [];
69
73
  // Tag-based filters are the main cross-cutting dimension for Thoughts
70
74
  filters = filters.concat(
71
- _joe.Filter.Options.tags({ group: 'tags', untagged: true, collapsed: true })
75
+ _joe.Filter.Options.tags({ group: 'tags', untagged: true, collapsed: true }),
76
+ _joe.Filter.Options.getDatasetPropertyValues('thought','created_by',{ group: 'created_by', collapsed: true }),
77
+ _joe.Filter.Options.getDatasetPropertyValues('thought','creator_type',{ group: 'creator_type', collapsed: true })
78
+
72
79
  );
73
80
 
81
+ // Creator_id filters with human-readable labels
82
+ try{
83
+ var dataset = _joe.getDataset('thought') || [];
84
+ var byCreator = {};
85
+ dataset.map(function(t){
86
+ if(!t.creator_id){ return; }
87
+ var key = t.creator_type+':'+t.creator_id;
88
+ if(!byCreator[key]){
89
+ // Try to resolve a nice display name
90
+ var name = t.creator_id;
91
+ if(t.creator_type === 'user' && _joe.Data.user){
92
+ var u = _joe.Data.user.find(function(x){ return x && x._id === t.creator_id; });
93
+ if(u){ name = u.fullname || u.name || u.email || u._id; }
94
+ }else if(t.creator_type === 'agent'){
95
+ // For agents we only have logical ids like thought_agent_default
96
+ name = t.creator_id;
97
+ }
98
+ byCreator[key] = {
99
+ name: name,
100
+ filter: { creator_type: t.creator_type, creator_id: t.creator_id }
101
+ };
102
+ }
103
+ });
104
+ var keys = Object.keys(byCreator);
105
+ if(keys.length){
106
+ filters.push({ group_start:'creator', collapsed:true });
107
+ keys.sort(function(a,b){
108
+ var na = byCreator[a].name.toLowerCase();
109
+ var nb = byCreator[b].name.toLowerCase();
110
+ if(na>nb) return 1;
111
+ if(na<nb) return -1;
112
+ return 0;
113
+ }).map(function(k){
114
+ filters.push({
115
+ name: byCreator[k].name,
116
+ filter: byCreator[k].filter
117
+ });
118
+ });
119
+ filters.push({ group_end:'creator' });
120
+ }
121
+ }catch(_e){}
122
+
74
123
  // Group thoughts by what they are about (about[].itemtype).
75
124
  // We build filters manually so that each itemtype gets its own filter
76
125
  // on the nested path "about.itemtype". JOE's search engine treats this
@@ -104,7 +153,7 @@ var thought = {
104
153
  },
105
154
  listView: {
106
155
  title: function (th) {
107
- var html = `<joe-subtext>${_joe.Utils.prettyPrintDTS(th.created)}</joe-subtext><joe-title>${th.name}</joe-title>`;
156
+ var html = `<joe-subtext>${th.created_by} - ${_joe.Utils.prettyPrintDTS(th.created)}</joe-subtext><joe-title>${th.name}</joe-title>`;
108
157
  html += `<joe-subtitle>${th.statement}</joe-subtitle>`;
109
158
 
110
159
  return html;
@@ -237,6 +286,8 @@ var thought = {
237
286
  locked: true
238
287
  },
239
288
  { name: 'created_by', type: 'string', locked: true },
289
+ { name: 'creator_type', type: 'select', values:['user','agent'], locked: true },
290
+ { name: 'creator_id', type: 'string', locked: true },
240
291
  {
241
292
  name: 'lineage',
242
293
  type: 'rendering',
@@ -272,6 +323,7 @@ var thought = {
272
323
  }
273
324
  });
274
325
  },
326
+ methods:{},
275
327
  idprop: '_id'
276
328
  };
277
329
 
@@ -21,7 +21,7 @@ var joewebconfig = {
21
21
  templatesDir:'_templates',
22
22
  //httpsPort:2100,
23
23
  default_schemas:['user','group','goal','initiative','event','report','tag','status','workflow','list','notification','note','include','instance','setting',
24
- 'ai_assistant','ai_conversation','ai_response','ai_tool','ai_prompt','ai_widget_conversation','thought'
24
+ 'ai_assistant','ai_conversation','ai_response','ai_tool','ai_prompt','ai_widget_conversation','ai_pipeline','thought'
25
25
  ]
26
26
  };
27
27