clementine-agent 1.18.76 → 1.18.77

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.
@@ -6368,7 +6368,9 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6368
6368
  // ── CRON CRUD routes (continued) ──────────────────────────────
6369
6369
  app.post('/api/cron', (req, res) => {
6370
6370
  try {
6371
- const { name, schedule, prompt, tier, enabled, work_dir, mode, max_hours, max_retries, after, agent, context, skills, allowedTools, allowedMcpServers, tags, category, predictable, } = req.body;
6371
+ const { name, schedule, prompt, tier, enabled, work_dir, mode, max_hours, max_retries, after, agent, context, skills, allowedTools, allowedMcpServers, tags, category, predictable,
6372
+ // PRD Phase 1 fields (camelCase from API; written as snake_case YAML).
6373
+ successCriteriaText, successSchema, addDirs, } = req.body;
6372
6374
  if (!name || !schedule || !prompt) {
6373
6375
  res.status(400).json({ error: 'name, schedule, and prompt are required' });
6374
6376
  return;
@@ -6427,6 +6429,16 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6427
6429
  // Predictable mode — default to true (contract execution) for new
6428
6430
  // tricks created via the dashboard. Mirror the MCP tool default.
6429
6431
  job.predictable = (predictable === false) ? false : true;
6432
+ // PRD Phase 1: goal-orientation fields (camelCase from API → snake_case YAML).
6433
+ if (typeof successCriteriaText === 'string' && successCriteriaText.trim()) {
6434
+ job.success_criteria_text = successCriteriaText.trim();
6435
+ }
6436
+ if (successSchema && typeof successSchema === 'object' && !Array.isArray(successSchema) && Object.keys(successSchema).length > 0) {
6437
+ job.success_schema = successSchema;
6438
+ }
6439
+ if (Array.isArray(addDirs) && addDirs.length) {
6440
+ job.add_dirs = addDirs.map(String).map((s) => s.trim()).filter(Boolean);
6441
+ }
6430
6442
  jobs.push(job);
6431
6443
  writeCronFileAt(cronFile, parsed, jobs);
6432
6444
  res.json({ ok: true, message: `Created cron job: ${name}` });
@@ -6568,6 +6580,36 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6568
6580
  if (updates.predictable !== undefined) {
6569
6581
  jobs[idx].predictable = Boolean(updates.predictable);
6570
6582
  }
6583
+ // PRD Phase 1 goal fields. set-when-non-empty / delete-when-cleared,
6584
+ // matching the existing trick-capability pattern above. The deprecated
6585
+ // success_criteria array is dropped on the first save through this path.
6586
+ if (updates.successCriteriaText !== undefined) {
6587
+ const v = typeof updates.successCriteriaText === 'string' ? updates.successCriteriaText.trim() : '';
6588
+ if (v) {
6589
+ jobs[idx].success_criteria_text = v;
6590
+ delete jobs[idx].success_criteria; // sunset the deprecated alias on first save
6591
+ }
6592
+ else {
6593
+ delete jobs[idx].success_criteria_text;
6594
+ }
6595
+ }
6596
+ if (updates.successSchema !== undefined) {
6597
+ const s = updates.successSchema;
6598
+ if (s && typeof s === 'object' && !Array.isArray(s) && Object.keys(s).length > 0) {
6599
+ jobs[idx].success_schema = s;
6600
+ }
6601
+ else {
6602
+ delete jobs[idx].success_schema;
6603
+ }
6604
+ }
6605
+ if (updates.addDirs !== undefined) {
6606
+ if (Array.isArray(updates.addDirs) && updates.addDirs.length) {
6607
+ jobs[idx].add_dirs = updates.addDirs.map(String).map((s) => s.trim()).filter(Boolean);
6608
+ }
6609
+ else {
6610
+ delete jobs[idx].add_dirs;
6611
+ }
6612
+ }
6571
6613
  if (updates.name !== undefined && updates.name !== bareJobName) {
6572
6614
  // Rename — check for duplicates
6573
6615
  const dup = jobs.find((j, i) => i !== idx && String(j.name ?? '').toLowerCase() === String(updates.name).toLowerCase());
@@ -19998,6 +20040,32 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19998
20040
  </div>
19999
20041
  </div>
20000
20042
 
20043
+ <!-- ── PRD Phase 1: Goal (success_criteria_text + success_schema) ──
20044
+ The single most important new field set. Without one of these, a
20045
+ run "finished"; with one, a run "accomplished what it was meant
20046
+ to". Banner warns (does not block) when neither is set. -->
20047
+ <div class="cron-section-card">
20048
+ <h4>Goal <span style="color:var(--text-muted);font-weight:normal;font-size:12px">— how do you know this task succeeded?</span></h4>
20049
+ <p class="cron-section-desc">Optional but strongly recommended. Use plain English (an evaluator agent grades the run) or a JSON Schema (validated against the agent's structured output).</p>
20050
+ <div id="cron-goal-warning" style="display:none;margin-bottom:12px;padding:10px 12px;border-radius:6px;background:rgba(245,158,11,0.10);border:1px solid rgba(245,158,11,0.30);color:var(--yellow);font-size:12px">
20051
+ ⚠ No goal set — runs will be marked "finished" but not "accomplished". Add a success criterion below or a JSON Schema.
20052
+ </div>
20053
+ <div class="form-group">
20054
+ <label class="form-label">Success criterion <span style="color:var(--text-muted);font-weight:normal">(plain English)</span></label>
20055
+ <textarea id="cron-success-criteria-text" rows="3" placeholder="e.g. 'A daily briefing email was sent to nathan@example.com containing the top 3 overnight items.'" oninput="updateGoalWarning()"></textarea>
20056
+ <div class="form-hint">An evaluator sub-agent reads the run's output and this criterion, then emits pass/fail with reasoning.</div>
20057
+ </div>
20058
+ <div class="form-group" style="margin-bottom:0">
20059
+ <details>
20060
+ <summary style="cursor:pointer;font-size:12px;color:var(--text-secondary);font-weight:500;padding:6px 0">▾ Success schema (JSON Schema, advanced)</summary>
20061
+ <div style="margin-top:8px">
20062
+ <textarea id="cron-success-schema" rows="6" placeholder='{ "type": "object", "required": ["sent"], "properties": { "sent": { "type": "boolean" } } }' style="font-family:'JetBrains Mono',monospace;font-size:11px" oninput="updateGoalWarning()"></textarea>
20063
+ <div class="form-hint">JSON Schema validated against the agent's <code>structured_output</code>. Mechanically successful = parses + validates.</div>
20064
+ </div>
20065
+ </details>
20066
+ </div>
20067
+ </div>
20068
+
20001
20069
  <!-- Skills & tools: pinned skills + MCP + tools + tags -->
20002
20070
  <div class="cron-section-card">
20003
20071
  <h4>Skills &amp; tools</h4>
@@ -20074,6 +20142,14 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20074
20142
  <div class="form-hint">Run inside a project directory. Agent gets that project's CLAUDE.md.</div>
20075
20143
  </div>
20076
20144
  </div>
20145
+ <!-- PRD Phase 1: read scope beyond cwd. One absolute path per line. -->
20146
+ <div class="form-row">
20147
+ <div class="form-group" style="flex:1">
20148
+ <label class="form-label">Additional read directories <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
20149
+ <textarea id="cron-add-dirs" rows="2" placeholder="/Users/me/notes&#10;/Users/me/clients/acme" style="font-family:'JetBrains Mono',monospace;font-size:11px"></textarea>
20150
+ <div class="form-hint">One absolute path per line. The agent gets read access to these in addition to the Project Context cwd.</div>
20151
+ </div>
20152
+ </div>
20077
20153
  <div class="form-row">
20078
20154
  <div class="form-group">
20079
20155
  <label class="form-label">Mode</label>
@@ -24719,6 +24795,18 @@ function renderCronLegacyBanner(job) {
24719
24795
  + '</div>';
24720
24796
  }
24721
24797
 
24798
+ // PRD Phase 1: show a non-blocking warning under the Goal section header
24799
+ // when neither success_criteria_text nor success_schema is set. The PRD's
24800
+ // "the run accomplished what it was supposed to" promise depends on at
24801
+ // least one of the two being present.
24802
+ function updateGoalWarning() {
24803
+ var sct = (document.getElementById('cron-success-criteria-text')?.value || '').trim();
24804
+ var ssc = (document.getElementById('cron-success-schema')?.value || '').trim();
24805
+ var warn = document.getElementById('cron-goal-warning');
24806
+ if (!warn) return;
24807
+ warn.style.display = (!sct && !ssc) ? '' : 'none';
24808
+ }
24809
+
24722
24810
  // One-click migration: flip predictable=true AND save immediately so the
24723
24811
  // user doesn't have to remember to also click Save Changes.
24724
24812
  async function enablePredictableFromBanner() {
@@ -24760,6 +24848,11 @@ function openCreateCronModal(agentSlug) {
24760
24848
  toggleUnleashedOptions();
24761
24849
  document.getElementById('cron-prompt').value = '';
24762
24850
  document.getElementById('cron-context').value = '';
24851
+ // PRD Phase 1 goal fields — empty by default. Warning banner will show.
24852
+ var sct = document.getElementById('cron-success-criteria-text'); if (sct) sct.value = '';
24853
+ var ssc = document.getElementById('cron-success-schema'); if (ssc) ssc.value = '';
24854
+ var addDirsEl = document.getElementById('cron-add-dirs'); if (addDirsEl) addDirsEl.value = '';
24855
+ if (typeof updateGoalWarning === 'function') updateGoalWarning();
24763
24856
  document.getElementById('cron-training-section').style.display = 'none';
24764
24857
  document.getElementById('cron-train-btn').style.display = '';
24765
24858
  resetCronTrainingChat();
@@ -24806,6 +24899,30 @@ function openEditCronModal(jobName) {
24806
24899
  toggleUnleashedOptions();
24807
24900
  document.getElementById('cron-prompt').value = job.prompt || '';
24808
24901
  document.getElementById('cron-context').value = job.context || '';
24902
+ // PRD Phase 1: load goal fields. Accept either casing — old YAML may have
24903
+ // success_criteria as a list (legacy); the parser already coalesces those
24904
+ // into successCriteriaText on read, but defend here too in case the API
24905
+ // shape differs from what the parser produces.
24906
+ var sctE = document.getElementById('cron-success-criteria-text');
24907
+ if (sctE) {
24908
+ var sctVal = job.successCriteriaText || job.success_criteria_text || '';
24909
+ if (!sctVal && Array.isArray(job.successCriteria || job.success_criteria)) {
24910
+ sctVal = (job.successCriteria || job.success_criteria || []).join('\\n');
24911
+ }
24912
+ sctE.value = sctVal;
24913
+ }
24914
+ var sscE = document.getElementById('cron-success-schema');
24915
+ if (sscE) {
24916
+ var sscObj = job.successSchema || job.success_schema;
24917
+ sscE.value = (sscObj && typeof sscObj === 'object') ? JSON.stringify(sscObj, null, 2) : '';
24918
+ }
24919
+ var addDirsE = document.getElementById('cron-add-dirs');
24920
+ if (addDirsE) {
24921
+ var addDirsArr = Array.isArray(job.addDirs) ? job.addDirs
24922
+ : (Array.isArray(job.add_dirs) ? job.add_dirs : []);
24923
+ addDirsE.value = addDirsArr.join('\\n');
24924
+ }
24925
+ if (typeof updateGoalWarning === 'function') updateGoalWarning();
24809
24926
  document.getElementById('cron-training-section').style.display = 'none';
24810
24927
  document.getElementById('cron-train-btn').style.display = '';
24811
24928
  resetCronTrainingChat();
@@ -25001,6 +25118,11 @@ function captureCronModalSnapshot() {
25001
25118
  v('cron-workdir'),
25002
25119
  v('cron-allowed-tools'),
25003
25120
  v('cron-category'),
25121
+ // PRD Phase 1 goal fields — included in dirty check so leaving the
25122
+ // modal with an unsaved success_schema or success_criteria_text prompts.
25123
+ v('cron-success-criteria-text'),
25124
+ v('cron-success-schema'),
25125
+ v('cron-add-dirs'),
25004
25126
  (document.getElementById('cron-predictable') || {}).checked ? '1' : '0',
25005
25127
  JSON.stringify(_cronSelectedSkills || []),
25006
25128
  JSON.stringify(_cronSelectedMcp || []),
@@ -25025,6 +25147,9 @@ function isCronModalDirty() {
25025
25147
  v('cron-workdir'),
25026
25148
  v('cron-allowed-tools'),
25027
25149
  v('cron-category'),
25150
+ v('cron-success-criteria-text'),
25151
+ v('cron-success-schema'),
25152
+ v('cron-add-dirs'),
25028
25153
  (document.getElementById('cron-predictable') || {}).checked ? '1' : '0',
25029
25154
  JSON.stringify(_cronSelectedSkills || []),
25030
25155
  JSON.stringify(_cronSelectedMcp || []),
@@ -25199,6 +25324,34 @@ async function saveCronJob() {
25199
25324
  }
25200
25325
  if (!prompt) { toast('Prompt is required — tell the agent what to do', 'error'); document.getElementById('cron-prompt').focus(); return; }
25201
25326
 
25327
+ // PRD Phase 1 goal fields. successCriteriaText is freeform; successSchema
25328
+ // is parsed JSON. Validate JSON early so the user gets a clean error before
25329
+ // the round-trip. Empty schema is fine — we just send {} or undefined.
25330
+ var successCriteriaText = (document.getElementById('cron-success-criteria-text')?.value || '').trim();
25331
+ var successSchemaRaw = (document.getElementById('cron-success-schema')?.value || '').trim();
25332
+ var successSchema;
25333
+ if (successSchemaRaw) {
25334
+ try {
25335
+ successSchema = JSON.parse(successSchemaRaw);
25336
+ if (!successSchema || typeof successSchema !== 'object' || Array.isArray(successSchema)) {
25337
+ toast('Success schema must be a JSON object', 'error');
25338
+ document.getElementById('cron-success-schema').focus();
25339
+ return;
25340
+ }
25341
+ } catch (e) {
25342
+ toast('Success schema is not valid JSON: ' + (e.message || String(e)), 'error');
25343
+ document.getElementById('cron-success-schema').focus();
25344
+ return;
25345
+ }
25346
+ }
25347
+ // add_dirs: one absolute path per line. Trim, dedupe, drop blanks.
25348
+ var addDirsRaw = (document.getElementById('cron-add-dirs')?.value || '').split(/\\r?\\n/);
25349
+ var addDirs = addDirsRaw.map(function(s){ return s.trim(); }).filter(Boolean);
25350
+ // Quick sanity — warn but don't block on relative paths.
25351
+ if (addDirs.some(function(p){ return !p.startsWith('/') && !p.startsWith('~'); })) {
25352
+ toast('Heads up: add_dirs entries should be absolute paths.', 'info');
25353
+ }
25354
+
25202
25355
  const body = {
25203
25356
  name, schedule, tier, prompt, enabled: true,
25204
25357
  work_dir: work_dir || undefined, mode, max_hours, max_retries, after, context,
@@ -25216,6 +25369,10 @@ async function saveCronJob() {
25216
25369
  tags: editingCronJob ? _cronTags : (_cronTags.length ? _cronTags : undefined),
25217
25370
  category: editingCronJob ? (category || '') : category,
25218
25371
  predictable,
25372
+ // PRD Phase 1 goal-orientation. PUT delete-on-empty pattern below.
25373
+ successCriteriaText: editingCronJob ? successCriteriaText : (successCriteriaText || undefined),
25374
+ successSchema: editingCronJob ? (successSchema || null) : (successSchema || undefined),
25375
+ addDirs: editingCronJob ? addDirs : (addDirs.length ? addDirs : undefined),
25219
25376
  };
25220
25377
 
25221
25378
  var wasEditing = !!editingCronJob;
@@ -113,6 +113,24 @@ export function parseCronJobs() {
113
113
  const successCriteria = Array.isArray(job.success_criteria)
114
114
  ? job.success_criteria.map(c => String(c))
115
115
  : undefined;
116
+ // PRD Phase 1: prefer success_criteria_text (free-form). On read, fall
117
+ // back to joining the legacy success_criteria string[] so legacy YAML
118
+ // keeps rendering in the new editor surface. Writes go to the new field.
119
+ let successCriteriaText = typeof job.success_criteria_text === 'string'
120
+ ? String(job.success_criteria_text)
121
+ : (typeof job.successCriteriaText === 'string' ? String(job.successCriteriaText) : undefined);
122
+ if (!successCriteriaText && Array.isArray(successCriteria) && successCriteria.length > 0) {
123
+ successCriteriaText = successCriteria.join('\n');
124
+ }
125
+ // PRD Phase 1: JSON Schema validated against ResultMessage.structured_output.
126
+ // Accept either snake_case (success_schema) or camelCase from API. Stored
127
+ // as a plain object; ajv is loaded lazily at validation time.
128
+ const successSchemaRaw = job.success_schema ?? job.successSchema;
129
+ const successSchema = (successSchemaRaw && typeof successSchemaRaw === 'object' && !Array.isArray(successSchemaRaw))
130
+ ? successSchemaRaw
131
+ : undefined;
132
+ // PRD Phase 1: read scope beyond cwd. Accept either casing.
133
+ const addDirs = normalizeStringArray(job.add_dirs ?? job.addDirs);
116
134
  const alwaysDeliver = job.always_deliver === true ? true : undefined;
117
135
  const context = job.context != null ? String(job.context) : undefined;
118
136
  const preCheck = job.pre_check != null ? String(job.pre_check) : undefined;
@@ -140,7 +158,8 @@ export function parseCronJobs() {
140
158
  }
141
159
  jobs.push({
142
160
  name, schedule, prompt, enabled, tier, maxTurns, model, workDir, mode,
143
- maxHours, maxRetries, after, successCriteria, alwaysDeliver, context, preCheck, agentSlug,
161
+ maxHours, maxRetries, after, successCriteria, successCriteriaText, successSchema, addDirs,
162
+ alwaysDeliver, context, preCheck, agentSlug,
144
163
  skills, allowedTools, allowedMcpServers, tags, category, predictable,
145
164
  });
146
165
  }
@@ -187,6 +206,18 @@ export function parseAgentCronJobs(agentsDir) {
187
206
  const successCriteria = Array.isArray(job.success_criteria)
188
207
  ? job.success_criteria.map(c => String(c))
189
208
  : undefined;
209
+ // PRD Phase 1 fields — symmetric with global parser above.
210
+ let successCriteriaText = typeof job.success_criteria_text === 'string'
211
+ ? String(job.success_criteria_text)
212
+ : (typeof job.successCriteriaText === 'string' ? String(job.successCriteriaText) : undefined);
213
+ if (!successCriteriaText && Array.isArray(successCriteria) && successCriteria.length > 0) {
214
+ successCriteriaText = successCriteria.join('\n');
215
+ }
216
+ const successSchemaRaw = job.success_schema ?? job.successSchema;
217
+ const successSchema = (successSchemaRaw && typeof successSchemaRaw === 'object' && !Array.isArray(successSchemaRaw))
218
+ ? successSchemaRaw
219
+ : undefined;
220
+ const addDirs = normalizeStringArray(job.add_dirs ?? job.addDirs);
190
221
  const context = job.context != null ? String(job.context) : undefined;
191
222
  const preCheck = job.pre_check != null ? String(job.pre_check) : undefined;
192
223
  // ── Trick capabilities — symmetric with global parser ─────────
@@ -210,7 +241,9 @@ export function parseAgentCronJobs(agentsDir) {
210
241
  allJobs.push({
211
242
  name: `${slug}:${name}`,
212
243
  schedule, prompt, enabled, tier, maxTurns, model, workDir,
213
- mode, maxHours, maxRetries, after, successCriteria, context, preCheck,
244
+ mode, maxHours, maxRetries, after,
245
+ successCriteria, successCriteriaText, successSchema, addDirs,
246
+ context, preCheck,
214
247
  agentSlug: slug,
215
248
  skills, allowedTools, allowedMcpServers, tags, category, predictable,
216
249
  });
package/dist/types.d.ts CHANGED
@@ -328,7 +328,22 @@ export interface CronJobDefinition {
328
328
  maxRetries?: number;
329
329
  after?: string;
330
330
  agentSlug?: string;
331
+ /** @deprecated Use successCriteriaText (free-text) or successSchema (JSON Schema)
332
+ * per PRD Phase 1. successCriteria is kept readable for one release; on read,
333
+ * parseCronJobs coalesces it into successCriteriaText. */
331
334
  successCriteria?: string[];
335
+ /** PRD Phase 1: free-text "this task is done when…". An evaluator sub-agent reads
336
+ * the run's final state and the criterion and emits a pass/fail with reasoning.
337
+ * Stored as RunEvaluation on the Run. Optional but recommended. */
338
+ successCriteriaText?: string;
339
+ /** PRD Phase 1: JSON Schema validated against ResultMessage.structured_output.
340
+ * If it parses, the run is mechanically successful. The Task editor shows a
341
+ * non-blocking "Goal not set" warning when neither this nor successCriteriaText
342
+ * is present. */
343
+ successSchema?: Record<string, unknown>;
344
+ /** PRD Phase 1: read scope beyond the cwd (workDir). Surfaced as a chip list
345
+ * in the editor's Scope tab. The runner passes these to the SDK as add_dirs. */
346
+ addDirs?: string[];
332
347
  alwaysDeliver?: boolean;
333
348
  context?: string;
334
349
  preCheck?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.76",
3
+ "version": "1.18.77",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",