clementine-agent 1.3.0 → 1.3.2

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.
@@ -11333,7 +11333,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
11333
11333
  <span id="builder-agent-label" style="padding:0;font-size:13px;color:var(--text-secondary);font-weight:500"></span>
11334
11334
  <input type="hidden" id="builder-agent" value="">
11335
11335
  <span style="flex:1"></span>
11336
- <button class="btn-sm" onclick="resetBuilder()" style="background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px">New</button>
11336
+ <button class="btn-sm btn-primary" onclick="newFromBuildHeader()" title="Create a new artifact for this tab" style="padding:4px 14px;border-radius:6px;cursor:pointer;font-size:12px">New</button>
11337
11337
  <button class="btn-sm" id="builder-test-btn" onclick="testBuilderSkill()" style="background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px;display:none">Test</button>
11338
11338
  <button class="btn-sm btn-primary" id="builder-save-btn" onclick="saveBuilderArtifact()" style="padding:4px 16px;font-size:12px;display:none">Save</button>
11339
11339
  </div>
@@ -11402,7 +11402,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
11402
11402
  <div onclick="_builderAddNodeOfKind('loop')" class="builder-palette-item" data-kind="loop">loop</div>
11403
11403
  </div>
11404
11404
  <!-- Slide-out config panel -->
11405
- <div id="builder-config-panel" style="display:none;position:absolute;right:0;top:0;bottom:0;width:340px;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-4px 0 16px rgba(0,0,0,0.15);z-index:12;display:flex;flex-direction:column"></div>
11405
+ <div id="builder-config-panel" style="display:none;position:absolute;right:0;top:0;bottom:0;width:340px;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-4px 0 16px rgba(0,0,0,0.15);z-index:12;flex-direction:column"></div>
11406
+ <!-- Empty-state CTA — visible when no workflow is open on the canvas -->
11407
+ <div id="builder-canvas-empty" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:14px;color:var(--text-muted);text-align:center;padding:32px;pointer-events:none">
11408
+ <div style="font-size:38px;opacity:0.4">&#128279;</div>
11409
+ <div style="font-size:14px;font-weight:500;color:var(--text-secondary)">No workflow open</div>
11410
+ <div style="font-size:12px;line-height:1.5;max-width:280px">
11411
+ Pick one from the dropdown above &mdash; or click <strong>New</strong> in the header to create one from scratch, or open the <strong>Templates</strong> tab for starter patterns.
11412
+ </div>
11413
+ </div>
11406
11414
  <div id="builder-canvas-footer" style="padding:6px 14px;border-top:1px solid var(--border);font-size:11px;color:var(--text-muted);display:flex;gap:14px;align-items:center">
11407
11415
  <span id="builder-canvas-status"></span>
11408
11416
  <span style="flex:1"></span>
@@ -14005,6 +14013,9 @@ function switchBuildTab(tab) {
14005
14013
  var workPane = document.getElementById('build-tab-workflows');
14006
14014
  var tplPane = document.getElementById('build-tab-templates');
14007
14015
  var headerStrip = document.getElementById('build-header-strip');
14016
+ // Always close any open workflow when changing tabs — switching context
14017
+ // is a clean slate, not a stale node hanging on the canvas.
14018
+ if (typeof closeBuilderCanvas === 'function') closeBuilderCanvas();
14008
14019
  if (tab === 'templates') {
14009
14020
  if (workPane) workPane.style.display = 'none';
14010
14021
  if (tplPane) tplPane.style.display = '';
@@ -14025,6 +14036,11 @@ function switchBuildTab(tab) {
14025
14036
  updateBuilderMode();
14026
14037
  }
14027
14038
  }
14039
+ // Preload Drawflow eagerly so the canvas is responsive when the
14040
+ // user picks a workflow (rather than waiting for the lazy load).
14041
+ if (typeof _ensureDrawflowLoaded === 'function') {
14042
+ _ensureDrawflowLoaded().catch(function() { /* */ });
14043
+ }
14028
14044
  // Focus chat input
14029
14045
  setTimeout(function() {
14030
14046
  var bi = document.getElementById('builder-input');
@@ -14033,6 +14049,38 @@ function switchBuildTab(tab) {
14033
14049
  }
14034
14050
  }
14035
14051
 
14052
+ // "New" button in the Build header strip — context-aware: prompts for a
14053
+ // name and creates the right artifact for the active tab. Workflows + Crons
14054
+ // route through the workflow_create surface; Skills falls back to the
14055
+ // existing chat-based Skill Studio reset.
14056
+ async function newFromBuildHeader() {
14057
+ var activeTab = document.querySelector('#build-tabs button.active')?.getAttribute('data-build-tab') || 'workflows';
14058
+ if (activeTab === 'skills') {
14059
+ if (typeof resetBuilder === 'function') resetBuilder();
14060
+ var bi = document.getElementById('builder-input');
14061
+ if (bi) bi.focus();
14062
+ return;
14063
+ }
14064
+ if (activeTab === 'templates') {
14065
+ toast('Pick a template to fork from the cards.', 'info');
14066
+ return;
14067
+ }
14068
+ var noun = activeTab === 'crons' ? 'cron' : 'workflow';
14069
+ var name = prompt('Name your new ' + noun + ':');
14070
+ if (!name || !name.trim()) return;
14071
+ try {
14072
+ var body = { name: name.trim() };
14073
+ if (activeTab === 'crons') body.schedule = '0 9 * * *'; // sensible default; user edits in canvas
14074
+ var r = await apiJson('POST', '/api/builder/workflows', body);
14075
+ if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
14076
+ if (r && r.id) {
14077
+ await refreshBuilderCanvasPicker(activeTab === 'crons' ? 'cron' : 'workflow');
14078
+ await openBuilderWorkflow(r.id);
14079
+ toast('Created ' + noun + ': ' + name, 'success');
14080
+ }
14081
+ } catch (err) { toast('Create error: ' + err, 'error'); }
14082
+ }
14083
+
14036
14084
  // ── Build templates: fork a starter pattern into a new workflow ─────
14037
14085
  async function forkBuildTemplate(templateId) {
14038
14086
  var templates = {
@@ -18287,6 +18335,9 @@ async function openBuilderWorkflow(id) {
18287
18335
  function _renderBuilderCanvas(drawflowData) {
18288
18336
  var host = document.getElementById('builder-canvas');
18289
18337
  if (!host) return;
18338
+ // Hide empty-state CTA — there's a workflow open now.
18339
+ var empty = document.getElementById('builder-canvas-empty');
18340
+ if (empty) empty.style.display = 'none';
18290
18341
  // Tear down previous editor
18291
18342
  if (_builderCanvasEditor) {
18292
18343
  try { _builderCanvasEditor.clear(); } catch (e) { /* ignore */ }
@@ -18296,13 +18347,16 @@ function _renderBuilderCanvas(drawflowData) {
18296
18347
  editor.reroute = true;
18297
18348
  editor.editor_mode = 'edit';
18298
18349
  editor.start();
18350
+ // Assign global BEFORE decoration runs — decoration reads node data via
18351
+ // editor.export() to recover stepIds (Drawflow doesn't preserve our
18352
+ // df-* attrs without a template).
18353
+ _builderCanvasEditor = editor;
18299
18354
  try {
18300
18355
  editor.import(drawflowData || { drawflow: { Home: { data: {} } } });
18301
18356
  _decorateBuilderNodes(host, _builderCanvasLastWorkflow);
18302
18357
  } catch (err) {
18303
18358
  host.innerHTML = '<div style="padding:24px;color:var(--red)">Failed to render canvas: ' + esc(String(err)) + '</div>';
18304
18359
  }
18305
- _builderCanvasEditor = editor;
18306
18360
  _bindBuilderCanvasEvents(editor);
18307
18361
  }
18308
18362
 
@@ -18394,35 +18448,30 @@ function _updateBuilderBannerFromValidation(v) {
18394
18448
  }
18395
18449
 
18396
18450
  function _decorateBuilderNodes(host, wf) {
18397
- if (!wf) return;
18451
+ if (!wf || !_builderCanvasEditor) return;
18452
+ // Pull each Drawflow node's data via the editor (which preserves the
18453
+ // stepId we set in stepToNodeData on the server side). Earlier code tried
18454
+ // to recover stepId from a df-stepId input, which Drawflow doesn't emit
18455
+ // unless you use templates — so every node decorated with the same step.
18456
+ // Now we look the step up by stepId directly.
18398
18457
  var byStepId = {};
18399
18458
  for (var i = 0; i < wf.steps.length; i++) byStepId[wf.steps[i].id] = wf.steps[i];
18400
- // Drawflow renders blank node bodies by default — overlay our own content.
18459
+ var allData = _builderCanvasEditor.export();
18460
+ var nodeData = (allData && allData.drawflow && allData.drawflow.Home && allData.drawflow.Home.data) || {};
18401
18461
  var nodes = host.querySelectorAll('.drawflow-node');
18402
18462
  nodes.forEach(function(nodeEl) {
18403
18463
  var contentEl = nodeEl.querySelector('.drawflow_content_node');
18404
18464
  if (!contentEl || contentEl.dataset._decorated) return;
18405
18465
  contentEl.dataset._decorated = '1';
18406
- var dataAttr = nodeEl.querySelector('input[df-stepId]');
18407
- var stepId = dataAttr ? dataAttr.value : null;
18408
- // Drawflow doesn't auto-bind without templates — derive from class instead
18409
- var classNames = (nodeEl.className || '').split(/\s+/).filter(function(c) { return c.indexOf('cl-node-') === 0; });
18410
- var kind = classNames.length ? classNames[0].replace('cl-node-', '') : 'prompt';
18411
- var title = '';
18412
- var body = '';
18413
- var matchedStep = null;
18414
- for (var j = 0; j < wf.steps.length; j++) {
18415
- var s = wf.steps[j];
18416
- if ((s.kind || 'prompt') === kind) { matchedStep = s; break; }
18417
- }
18418
- if (!matchedStep) {
18419
- // Best-effort: use first step
18420
- matchedStep = wf.steps[0];
18421
- }
18422
- title = (matchedStep ? matchedStep.id : '?');
18423
- body = _summarizeStep(matchedStep, kind);
18466
+ var numericId = (nodeEl.id || '').replace(/^node-/, '');
18467
+ var data = (nodeData[numericId] && nodeData[numericId].data) || {};
18468
+ var stepId = data.stepId || null;
18469
+ var step = stepId ? byStepId[stepId] : null;
18470
+ var kind = (data.kind || (step && step.kind) || 'prompt');
18471
+ var title = step ? step.id : (stepId || '?');
18472
+ var body = _summarizeStep(step || data, kind);
18424
18473
  contentEl.innerHTML =
18425
- '<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' · ' + esc(title) + '</div>' +
18474
+ '<div style="font-weight:600;font-size:12px;margin-bottom:4px;color:#fff;text-transform:lowercase">' + esc(kind) + ' &middot; ' + esc(title) + '</div>' +
18426
18475
  '<div style="font-size:11px;color:rgba(255,255,255,0.85);line-height:1.35;max-height:80px;overflow:hidden">' + esc(body) + '</div>';
18427
18476
  });
18428
18477
  }
@@ -18450,6 +18499,14 @@ function closeBuilderCanvas() {
18450
18499
  if (idEl) idEl.textContent = '';
18451
18500
  var banner = document.getElementById('builder-canvas-banner');
18452
18501
  if (banner) banner.style.display = 'none';
18502
+ // Reset picker if it has a stale selection
18503
+ var picker = document.getElementById('builder-canvas-picker');
18504
+ if (picker && picker.value) picker.value = '';
18505
+ // Close config panel if it was open
18506
+ if (typeof _closeNodeConfigPanel === 'function') _closeNodeConfigPanel();
18507
+ // Show empty-state CTA
18508
+ var empty = document.getElementById('builder-canvas-empty');
18509
+ if (empty) empty.style.display = 'flex';
18453
18510
  }
18454
18511
 
18455
18512
  async function validateBuilderCanvas() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",