json-object-editor 0.10.665 → 0.10.670

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 CHANGED
@@ -1,3 +1,5 @@
1
+ ## 0.10.668 - The Workflow Widget
2
+
1
3
  ## 0.10.665
2
4
 
3
5
  - AI Response JSON extraction and audit trail improvements
@@ -0,0 +1,216 @@
1
+ # Phase 1 Progress Tracking - Testing Steps
2
+
3
+ ## Overview
4
+ Phase 1 implements client-side multi-job progress tracking UI. Jobs are tracked per button, with support for single and multiple jobs per button.
5
+
6
+ ## Token Format
7
+ - **select_prompt**: Button token: `{objectId}_select_prompt_{timestamp}_{random}`, Job token: `{objectId}_{promptId}_{timestamp}_{random}`
8
+ - **ai:prompt (autofill)**: Token: `{objectId}_{fieldId}_{timestamp}_{random}`
9
+ - **proposeThought**: Token: `{objectId}_proposeThought_{timestamp}_{random}`
10
+
11
+ ## Test 1: ai:prompt Button (Single Job) - Task Description Field
12
+
13
+ ### Setup
14
+ 1. Open a task object in JOE
15
+ 2. Navigate to the "description" field (should have an "AI" button)
16
+
17
+ ### Test Steps
18
+ 1. **Verify button rendering**:
19
+ - Button should have class `joe-ai-button` and `joe-ai-autofill-button`
20
+ - Button should have `data-progress-token` attribute (format: `{objectId}_description_{timestamp}_{random}`)
21
+ - Button should have `data-field-name="description"` attribute
22
+ - Progress div should exist below button with `data-for-token` matching button token
23
+ - Jobs list container should exist below progress div
24
+
25
+ 2. **Click AI button**:
26
+ - Button should disable and show loading state
27
+ - Button text should change to "Running..." or show percentage if available
28
+ - Progress text field should show "Starting..." or progress message
29
+ - Jobs list should show one job: "AI Autofill - Running..."
30
+
31
+ 3. **During execution** (if progress updates are sent):
32
+ - Button text should update with percentage: "Running... X%"
33
+ - Progress text should show detailed message
34
+ - Job in list should show percentage
35
+
36
+ 4. **On completion**:
37
+ - Button text should show "Complete" briefly, then restore to "AI"
38
+ - Button should re-enable
39
+ - Progress text should clear after 2 seconds
40
+ - Job should be removed from list
41
+
42
+ 5. **On error**:
43
+ - Button should re-enable
44
+ - Job should be cleared
45
+ - Error alert should appear
46
+
47
+ ### Expected Results
48
+ - Single job tracked correctly
49
+ - Button shows percentage when single job is active
50
+ - UI updates properly
51
+ - Job cleared on completion/error
52
+
53
+ ---
54
+
55
+ ## Test 2: select_prompt Field (Multiple Jobs)
56
+
57
+ ### Setup
58
+ 1. Open a protocol object (or any object with `select_prompt` field)
59
+ 2. Navigate to the "Run AI Prompt" field
60
+
61
+ ### Test Steps
62
+ 1. **Verify button rendering**:
63
+ - Button token format: `{objectId}_select_prompt_{timestamp}_{random}`
64
+ - Progress and jobs containers exist
65
+
66
+ 2. **Run first prompt**:
67
+ - Select a prompt from dropdown
68
+ - Click "Run AI Prompt"
69
+ - Button should disable
70
+ - Job token should be generated: `{objectId}_{promptId}_{timestamp}_{random}`
71
+ - Jobs list should show: "{Prompt Name} - Running..."
72
+ - Button text should show percentage (single job)
73
+
74
+ 3. **Run second prompt** (while first is running):
75
+ - Select a different prompt
76
+ - Click "Run AI Prompt" again
77
+ - Second job should be added to jobs list
78
+ - Button text should change to "2 jobs running"
79
+ - Progress text field should clear (multiple jobs shown in list)
80
+
81
+ 4. **Verify multiple jobs display**:
82
+ - Jobs list should show both jobs with their names and statuses
83
+ - Each job should show percentage or "Running..."
84
+
85
+ 5. **Complete jobs**:
86
+ - As jobs complete, they should be removed from list
87
+ - Button text should update: "2 jobs running" → "1 job running" → percentage → "Complete" → original text
88
+ - When all jobs complete, button should re-enable
89
+
90
+ ### Expected Results
91
+ - Multiple jobs tracked correctly
92
+ - Button shows "X jobs running" when multiple active
93
+ - Jobs list displays all active jobs
94
+ - Button shows percentage when only one job remains
95
+ - All jobs cleared when complete
96
+
97
+ ---
98
+
99
+ ## Test 3: proposeThought Field (Single Job)
100
+
101
+ ### Setup
102
+ 1. Open a task object
103
+ 2. Navigate to "Propose Thought" field
104
+
105
+ ### Test Steps
106
+ 1. **Verify button rendering**:
107
+ - Button token format: `{objectId}_proposeThought_{timestamp}_{random}`
108
+ - Progress and jobs containers exist
109
+
110
+ 2. **Run thought agent**:
111
+ - Click "Run Thought Agent"
112
+ - Button should disable
113
+ - Job should be tracked (token: `{objectId}_proposeThought_{timestamp}_{random}`)
114
+ - Button text should show "Running..." or percentage
115
+ - Progress text should show status
116
+
117
+ 3. **On completion**:
118
+ - Button should re-enable
119
+ - Job should be cleared
120
+ - UI should restore
121
+
122
+ ### Expected Results
123
+ - Single job tracked correctly
124
+ - UI updates properly
125
+ - Job cleared on completion
126
+
127
+ ---
128
+
129
+ ## Test 4: Navigation (Field Rerender)
130
+
131
+ ### Setup
132
+ 1. Start a job on any field
133
+ 2. Navigate away (list view or different object)
134
+ 3. Navigate back to the same object
135
+
136
+ ### Test Steps
137
+ 1. **Before navigation**:
138
+ - Note the button token
139
+ - Start a job
140
+ - Verify job is tracked
141
+
142
+ 2. **After navigation**:
143
+ - Field rerenders with new button token
144
+ - Old job tracking is lost (expected for Phase 1)
145
+ - New button has no active jobs
146
+
147
+ ### Expected Results
148
+ - Field rerender creates new button with new token
149
+ - Old jobs are not visible (Phase 2 will add server-side reconnection)
150
+ - New button works normally
151
+
152
+ ---
153
+
154
+ ## Test 5: Error Handling
155
+
156
+ ### Test Steps
157
+ 1. **Network error**:
158
+ - Start a job
159
+ - Simulate network failure (disable network or invalid endpoint)
160
+ - Verify error alert appears
161
+ - Verify job is cleared
162
+ - Verify button re-enables
163
+
164
+ 2. **Server error**:
165
+ - Start a job with invalid parameters
166
+ - Verify error message appears
167
+ - Verify job is cleared
168
+ - Verify button re-enables
169
+
170
+ ### Expected Results
171
+ - Errors are handled gracefully
172
+ - Jobs are cleared on error
173
+ - UI is restored
174
+
175
+ ---
176
+
177
+ ## Test 6: Multiple Windows
178
+
179
+ ### Setup
180
+ 1. Open same object in two browser windows/tabs
181
+
182
+ ### Test Steps
183
+ 1. **Window 1**:
184
+ - Start a job
185
+ - Verify job is tracked
186
+
187
+ 2. **Window 2**:
188
+ - Should have independent button token
189
+ - Can start separate job
190
+ - Jobs are tracked independently
191
+
192
+ ### Expected Results
193
+ - Each window has independent job tracking
194
+ - No conflicts between windows
195
+
196
+ ---
197
+
198
+ ## Debugging
199
+
200
+ ### Console Checks
201
+ - `Ai.activeJobs` Map should contain button tokens as keys
202
+ - Each value should be an array of job objects
203
+ - Job objects should have: `token`, `promptId`, `promptName`, `progress`, `total`, `message`
204
+
205
+ ### Common Issues
206
+ 1. **Button not found**: Check `data-progress-token` attribute exists
207
+ 2. **Jobs not showing**: Check `Ai.activeJobs` Map has entries
208
+ 3. **UI not updating**: Check `renderJobsList` is being called
209
+ 4. **Token format wrong**: Verify token matches expected format for field type
210
+
211
+ ---
212
+
213
+ ## Notes
214
+ - Phase 1 is client-side only. Server-side progress tracking comes in Phase 2.
215
+ - Jobs are lost on page refresh or field rerender (Phase 2 will add persistence).
216
+ - Progress updates require server-side implementation (Phase 2/3).
@@ -0,0 +1,218 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>JOE AI Jobs Test</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <style>
8
+ body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;}
9
+ label{display:block;margin:8px 0 4px}
10
+ select, input, textarea, button{font-size:14px;padding:6px 12px}
11
+ textarea{width:100%;height:160px;font-family:ui-monospace,Menlo,Consolas,monospace}
12
+ pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:10px;overflow:auto;max-height:400px}
13
+ .row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin:8px 0}
14
+ .small{font-size:12px;color:#666}
15
+ .bad{color:#b00020}
16
+ .good{color:#0a7d00}
17
+ .section{margin:20px 0;padding:15px;border:1px solid #e1e4e8;border-radius:4px}
18
+ button{cursor:pointer;background:#0366d6;color:white;border:none;border-radius:4px}
19
+ button:hover{background:#0256c2}
20
+ button:disabled{background:#ccc;cursor:not-allowed}
21
+ input[type="text"]{min-width:300px;padding:6px}
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div id="mcp-nav"></div>
26
+ <script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
27
+ <h1>JOE AI Jobs Test</h1>
28
+ <div class="small">View and test active AI job tracking. Jobs are keyed by {objectId}_{identifier}.</div>
29
+
30
+ <div class="section">
31
+ <h3>All Active Jobs</h3>
32
+ <div class="row">
33
+ <button id="refreshAll">Refresh</button>
34
+ <span id="allStatus" class="small"></span>
35
+ </div>
36
+ <pre id="allJobsResult"></pre>
37
+ </div>
38
+
39
+ <div class="section">
40
+ <h3>View Active Jobs for Object</h3>
41
+ <div class="row">
42
+ <label for="objectId">Object ID:</label>
43
+ <input id="objectId" type="text" placeholder="e.g. 5ce3d147-f37d-4c8f-9c00-bc185182c4d5" />
44
+ <button id="loadJobs">Load Jobs</button>
45
+ <span id="loadStatus" class="small"></span>
46
+ </div>
47
+ <pre id="jobsResult"></pre>
48
+ </div>
49
+
50
+ <div class="section">
51
+ <h3>Example Job Tokens</h3>
52
+ <div class="small">Use these formats to understand token structure:</div>
53
+ <pre id="examples">select_prompt: {objectId}_select_prompt_{timestamp}_{random}
54
+ Example: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_select_prompt_1771022571836_abc123
55
+ Lookup Key: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_select_prompt
56
+
57
+ Job token (when prompt selected): {objectId}_{promptId}_{timestamp}_{random}
58
+ Example: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_promptId123_1771022571837_xyz789
59
+ Lookup Key: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_promptId123
60
+
61
+ ai:prompt (autofill): {objectId}_{fieldId}_{timestamp}_{random}
62
+ Example: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_description_1771022571838_def456
63
+ Lookup Key: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_description
64
+
65
+ proposeThought: {objectId}_proposeThought_{timestamp}_{random}
66
+ Example: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_proposeThought_1771022571839_ghi789
67
+ Lookup Key: 5ce3d147-f37d-4c8f-9c00-bc185182c4d5_proposeThought</pre>
68
+ </div>
69
+
70
+ <div class="section">
71
+ <h3>Example Job JSON</h3>
72
+ <div class="small">Example job data structure (for reference):</div>
73
+ <pre id="exampleJson">{
74
+ "token": "5ce3d147-f37d-4c8f-9c00-bc185182c4d5_promptId123_1771022571837_xyz789",
75
+ "startTime": "2025-01-15T10:30:00.000Z",
76
+ "status": "running",
77
+ "promptId": "promptId123",
78
+ "promptName": "Generate Summary",
79
+ "fieldId": null,
80
+ "progress": 45,
81
+ "total": 100,
82
+ "message": "Processing files..."
83
+ }</pre>
84
+ </div>
85
+
86
+ <div class="section">
87
+ <h3>Test extractKey Function</h3>
88
+ <div class="row">
89
+ <label for="testToken">Token:</label>
90
+ <input id="testToken" type="text" placeholder="Paste a token here" style="flex:1" />
91
+ <button id="testExtract">Test extractKey</button>
92
+ </div>
93
+ <pre id="extractResult"></pre>
94
+ </div>
95
+
96
+ <script>
97
+ // Load all active jobs on page load
98
+ function loadAllJobs() {
99
+ var statusEl = document.getElementById('allStatus');
100
+ var resultEl = document.getElementById('allJobsResult');
101
+
102
+ statusEl.textContent = 'Loading...';
103
+ statusEl.className = 'small';
104
+ resultEl.textContent = '';
105
+
106
+ fetch('/API/aijobs')
107
+ .then(function(res) {
108
+ if (!res.ok) {
109
+ throw new Error('HTTP ' + res.status + ': ' + res.statusText);
110
+ }
111
+ return res.json();
112
+ })
113
+ .then(function(data) {
114
+ var count = data.count || 0;
115
+ var lookupKeys = data.lookupKeys || 0;
116
+ var jobsArray = data.jobs || [];
117
+ statusEl.textContent = 'Success - ' + count + ' job(s) across ' + lookupKeys + ' lookup key(s)';
118
+ statusEl.className = 'small good';
119
+
120
+ // Format for display
121
+ var displayData = {
122
+ count: count,
123
+ lookupKeys: lookupKeys,
124
+ jobs: jobsArray
125
+ };
126
+ resultEl.textContent = JSON.stringify(displayData, null, 2);
127
+ })
128
+ .catch(function(e) {
129
+ statusEl.textContent = 'Error: ' + e.message;
130
+ statusEl.className = 'small bad';
131
+ resultEl.textContent = 'Error: ' + e.message;
132
+ });
133
+ }
134
+
135
+ // Load on page load
136
+ if (document.readyState === 'loading') {
137
+ document.addEventListener('DOMContentLoaded', loadAllJobs);
138
+ } else {
139
+ loadAllJobs();
140
+ }
141
+
142
+ document.getElementById('refreshAll').addEventListener('click', loadAllJobs);
143
+
144
+ document.getElementById('loadJobs').addEventListener('click', async function() {
145
+ var objectId = document.getElementById('objectId').value.trim();
146
+ var statusEl = document.getElementById('loadStatus');
147
+ var resultEl = document.getElementById('jobsResult');
148
+
149
+ if (!objectId) {
150
+ statusEl.textContent = 'Please enter an object ID';
151
+ statusEl.className = 'small bad';
152
+ return;
153
+ }
154
+
155
+ statusEl.textContent = 'Loading...';
156
+ statusEl.className = 'small';
157
+ resultEl.textContent = '';
158
+
159
+ try {
160
+ var res = await fetch('/API/aijobs/' + encodeURIComponent(objectId));
161
+ if (!res.ok) {
162
+ throw new Error('HTTP ' + res.status + ': ' + res.statusText);
163
+ }
164
+ var data = await res.json();
165
+ statusEl.textContent = 'Success - ' + (data.count || 0) + ' job(s)';
166
+ statusEl.className = 'small good';
167
+ resultEl.textContent = JSON.stringify(data, null, 2);
168
+ } catch (e) {
169
+ statusEl.textContent = 'Error: ' + e.message;
170
+ statusEl.className = 'small bad';
171
+ resultEl.textContent = 'Error: ' + e.message;
172
+ }
173
+ });
174
+
175
+ document.getElementById('testExtract').addEventListener('click', function() {
176
+ var token = document.getElementById('testToken').value.trim();
177
+ var resultEl = document.getElementById('extractResult');
178
+
179
+ if (!token) {
180
+ resultEl.textContent = 'Please enter a token';
181
+ return;
182
+ }
183
+
184
+ // Token format: objectId_identifier_timestamp_random
185
+ // Extract: objectId_identifier (everything before last 2 underscores)
186
+ var parts = token.split('_');
187
+ if (parts.length < 3) {
188
+ resultEl.textContent = 'Invalid token format. Need at least 3 parts separated by underscores.';
189
+ return;
190
+ }
191
+
192
+ var lookupKey = parts.slice(0, -2).join('_');
193
+ var timestamp = parts[parts.length - 2];
194
+ var random = parts[parts.length - 1];
195
+
196
+ resultEl.textContent = JSON.stringify({
197
+ token: token,
198
+ lookupKey: lookupKey,
199
+ timestamp: timestamp,
200
+ random: random,
201
+ parts: parts.length
202
+ }, null, 2);
203
+ });
204
+
205
+ // Auto-load on Enter key
206
+ document.getElementById('objectId').addEventListener('keypress', function(e) {
207
+ if (e.key === 'Enter') {
208
+ document.getElementById('loadJobs').click();
209
+ }
210
+ });
211
+ document.getElementById('testToken').addEventListener('keypress', function(e) {
212
+ if (e.key === 'Enter') {
213
+ document.getElementById('testExtract').click();
214
+ }
215
+ });
216
+ </script>
217
+ </body>
218
+ </html>
package/_www/mcp-nav.js CHANGED
@@ -1,25 +1,33 @@
1
1
  ;(function(){
2
+ // Creates navigation element with two rows of links and info display. Returns the nav DOM element.
2
3
  function buildNav(){
3
4
  var nav = document.createElement('nav');
4
- nav.setAttribute('style','display:flex;flex-direction:column;gap:6px;margin-bottom:8px');
5
+ nav.setAttribute('style','display:flex;flex-direction:column;gap:6px;margin-bottom:8px;font-size:14px;');
5
6
  var infoRow = '<div id="mcp-nav-info" class="small" style="opacity:.8"></div>';
6
- var linksRow = [
7
+ var linksRow1 = [
7
8
  '<div style="display:flex;gap:10px;align-items:center">',
8
- '<a href="/mcp-test.html" target="mcp_test_win" rel="noopener">MCP Test</a>',
9
- '<a href="/mcp-export.html" target="mcp_export_win" rel="noopener">MCP Export</a>',
10
- '<a href="/mcp-schemas.html" target="mcp_schemas_win" rel="noopener">Schemas</a>',
11
- '<a href="/matrix.html" target="matrix_win" rel="noopener">Matrix</a>',
12
- '<a href="/mcp-prompt.html" target="mcp_prompt_win" rel="noopener">MCP Prompt</a>',
13
- '<a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>',
14
- '<a href="/plugins-test.html" target="plugins_test_win" rel="noopener">Plugins</a>',
9
+ '<a href="/mcp-test.html" target="mcp_test_win">MCP Test</a>',
10
+ '<a href="/mcp-export.html" target="mcp_export_win">MCP Export</a>',
11
+ '<a href="/mcp-prompt.html" target="mcp_prompt_win">MCP Prompt</a>',
12
+ '<a href="/.well-known/mcp/manifest.json" target="mcp_manifest_win">MCP Manifest</a>',
15
13
  '<span style="margin-left:auto"></span>',
16
- '<a href="/privacy" target="privacy_win" rel="noopener">Privacy</a>',
17
- '<a href="/terms" target="terms_win" rel="noopener">Terms</a>',
14
+ '<a href="/privacy" target="privacy_win">Privacy</a>',
15
+ '<a href="/terms" target="terms_win">Terms</a>',
18
16
  '</div>'
19
17
  ].join('');
20
- nav.innerHTML = infoRow + linksRow;
18
+ var linksRow2 = [
19
+ '<div style="display:flex;gap:10px;align-items:center">',
20
+ '<a href="/mcp-schemas.html" target="mcp_schemas_win">Schemas</a>',
21
+ '<a href="/matrix.html" target="matrix_win">Matrix</a>',
22
+ '<a href="/ai-widget-test.html" target="ai_widget_test_win">AI Widget</a>',
23
+ '<a href="/ai-jobs.html" target="ai_jobs_win">AI Jobs</a>',
24
+ '<a href="/plugins-test.html" target="plugins_test_win">Plugins</a>',
25
+ '</div>'
26
+ ].join('');
27
+ nav.innerHTML = infoRow + linksRow1 + linksRow2;
21
28
  return nav;
22
29
  }
30
+ // Inserts navigation into DOM, replacing placeholder if present or prepending to body. No return value.
23
31
  function insert(){
24
32
  var placeholder = document.getElementById('mcp-nav');
25
33
  var nav = buildNav();
@@ -29,6 +37,7 @@
29
37
  document.body.insertBefore(nav, document.body.firstChild);
30
38
  }
31
39
  }
40
+ // Fetches MCP manifest and updates info display with instance name, version, and hostname. No return value.
32
41
  async function updateInstance(){
33
42
  try{
34
43
  var res = await fetch('/.well-known/mcp/manifest.json');