clementine-agent 1.18.66 → 1.18.68
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/dist/agent/run-agent-cron.d.ts +18 -1
- package/dist/agent/run-agent-cron.js +24 -8
- package/dist/cli/dashboard.js +96 -11
- package/dist/dashboard/build-operations.d.ts +4 -0
- package/dist/dashboard/build-operations.js +1 -0
- package/dist/gateway/cron-scheduler.js +7 -4
- package/dist/gateway/router.d.ts +3 -1
- package/dist/gateway/router.js +4 -1
- package/dist/tools/admin-tools.js +25 -6
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
|
@@ -62,9 +62,15 @@ export interface SkillContextResult {
|
|
|
62
62
|
* that don't resolve are surfaced via `missing[]` (warned, never fatal) so
|
|
63
63
|
* the dashboard can flag broken references.
|
|
64
64
|
*
|
|
65
|
+
* When `opts.skipAutoMatch` is true (predictable mode), only pinned skills
|
|
66
|
+
* load — the runtime keyword/semantic match is skipped entirely. The trick
|
|
67
|
+
* runs with ONLY the skills the user explicitly attached.
|
|
68
|
+
*
|
|
65
69
|
* Exported only for testability — the production caller is `runAgentCron`.
|
|
66
70
|
*/
|
|
67
|
-
export declare function buildSkillContext(jobName: string, jobPrompt: string, agentSlug: string | undefined, pinnedSkills: string[] | undefined, memoryStore?: MemoryStore | null
|
|
71
|
+
export declare function buildSkillContext(jobName: string, jobPrompt: string, agentSlug: string | undefined, pinnedSkills: string[] | undefined, memoryStore?: MemoryStore | null, opts?: {
|
|
72
|
+
skipAutoMatch?: boolean;
|
|
73
|
+
}): Promise<SkillContextResult>;
|
|
68
74
|
/** Minimal interface for the post-task reflection + skill extraction
|
|
69
75
|
* hooks. Lets `runAgentCron` stay decoupled from the full
|
|
70
76
|
* PersonalAssistant import while still benefiting from the existing
|
|
@@ -115,6 +121,13 @@ export interface RunAgentCronOptions {
|
|
|
115
121
|
* Applied after `buildExtraMcpForRunAgent` runs, so the effective set
|
|
116
122
|
* is `profile ∩ trick`. */
|
|
117
123
|
allowedMcpServers?: string[];
|
|
124
|
+
/** Predictable mode — when true, the runner skips the auto-injected
|
|
125
|
+
* context blocks (MEMORY.md, team comms, delegation queue) and the
|
|
126
|
+
* auto-matched skill search. The trick runs with ONLY what was
|
|
127
|
+
* explicitly attached: prompt, criteria, pinned skills, linked goals,
|
|
128
|
+
* prior progress. The fix for fire-time memory drift. Undefined =
|
|
129
|
+
* legacy behavior (inject everything). */
|
|
130
|
+
predictable?: boolean;
|
|
118
131
|
}
|
|
119
132
|
export interface RunAgentCronResult extends RunAgentResult {
|
|
120
133
|
/** The final prompt that was sent to the agent (after context injection).
|
|
@@ -167,6 +180,10 @@ export interface CronExecutionPlan {
|
|
|
167
180
|
maxBudgetUsd: number | undefined;
|
|
168
181
|
agentSlug: string | undefined;
|
|
169
182
|
ownerName: string;
|
|
183
|
+
/** Whether the trick is in predictable (contract) mode — true means
|
|
184
|
+
* MEMORY.md / team / delegation / auto-skills were intentionally
|
|
185
|
+
* skipped. Used by the Preview verdict line. */
|
|
186
|
+
predictable: boolean;
|
|
170
187
|
}
|
|
171
188
|
/**
|
|
172
189
|
* Plan a cron run — assemble all context, resolve skills, intersect tool/MCP
|
|
@@ -226,9 +226,13 @@ function buildCriteriaContext(successCriteria) {
|
|
|
226
226
|
* that don't resolve are surfaced via `missing[]` (warned, never fatal) so
|
|
227
227
|
* the dashboard can flag broken references.
|
|
228
228
|
*
|
|
229
|
+
* When `opts.skipAutoMatch` is true (predictable mode), only pinned skills
|
|
230
|
+
* load — the runtime keyword/semantic match is skipped entirely. The trick
|
|
231
|
+
* runs with ONLY the skills the user explicitly attached.
|
|
232
|
+
*
|
|
229
233
|
* Exported only for testability — the production caller is `runAgentCron`.
|
|
230
234
|
*/
|
|
231
|
-
export async function buildSkillContext(jobName, jobPrompt, agentSlug, pinnedSkills, memoryStore) {
|
|
235
|
+
export async function buildSkillContext(jobName, jobPrompt, agentSlug, pinnedSkills, memoryStore, opts) {
|
|
232
236
|
const applied = [];
|
|
233
237
|
const missing = [];
|
|
234
238
|
try {
|
|
@@ -259,8 +263,10 @@ export async function buildSkillContext(jobName, jobPrompt, agentSlug, pinnedSki
|
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
// 2. Auto-match fills the remainder, deduped against pins.
|
|
266
|
+
// In predictable (contract) mode we skip this entirely — only
|
|
267
|
+
// pinned skills load, the runtime keyword/semantic search is off.
|
|
262
268
|
const remaining = MAX_INJECTED_SKILLS - prepared.length;
|
|
263
|
-
if (remaining > 0) {
|
|
269
|
+
if (remaining > 0 && !opts?.skipAutoMatch) {
|
|
264
270
|
const matched = searchSkills(skillQuery, remaining + (pinnedSkills?.length ?? 0), agentSlug, { suppressedNames });
|
|
265
271
|
for (const m of matched) {
|
|
266
272
|
if (prepared.length >= MAX_INJECTED_SKILLS)
|
|
@@ -320,13 +326,22 @@ export async function buildCronExecutionPlan(opts) {
|
|
|
320
326
|
const tier = opts.tier ?? 1;
|
|
321
327
|
const agentSlug = opts.profile?.slug;
|
|
322
328
|
const ownerName = process.env.OWNER_NAME ?? 'the user';
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
// ── Predictable (contract) mode ────────────────────────────────────
|
|
330
|
+
// When `predictable: true`, the trick runs with ONLY what was explicitly
|
|
331
|
+
// attached — prompt, criteria, pinned skills, linked goals, prior progress.
|
|
332
|
+
// We skip MEMORY.md, team comms, delegation queue, and the runtime skill
|
|
333
|
+
// auto-match. This is the fix for the email-cadence failure mode where the
|
|
334
|
+
// agent agreed to a plan in chat then re-derived from drifted memory at
|
|
335
|
+
// fire time. Legacy tricks (predictable === undefined) preserve existing
|
|
336
|
+
// behavior so we don't surprise anyone.
|
|
337
|
+
const predictable = opts.predictable === true;
|
|
338
|
+
const memoryContext = predictable ? '' : buildAutonomousMemoryContext(opts.profile);
|
|
339
|
+
const progressContext = buildProgressContext(opts.jobName); // opt-in via cron_progress writes
|
|
340
|
+
const goalContext = buildGoalContext(opts.jobName); // explicit links; not auto-inferred
|
|
341
|
+
const delegationContext = predictable ? '' : buildDelegationContext(agentSlug);
|
|
342
|
+
const teamContext = predictable ? '' : buildTeamContext(agentSlug);
|
|
328
343
|
const criteriaContext = buildCriteriaContext(opts.successCriteria);
|
|
329
|
-
const skillResult = await buildSkillContext(opts.jobName, opts.jobPrompt, agentSlug, opts.pinnedSkills, opts.memoryStore);
|
|
344
|
+
const skillResult = await buildSkillContext(opts.jobName, opts.jobPrompt, agentSlug, opts.pinnedSkills, opts.memoryStore, { skipAutoMatch: predictable });
|
|
330
345
|
const skillContext = skillResult.text;
|
|
331
346
|
const howToRespond = `## How to respond\n` +
|
|
332
347
|
`You're sending this directly to ${ownerName} as a DM. ` +
|
|
@@ -390,6 +405,7 @@ export async function buildCronExecutionPlan(opts) {
|
|
|
390
405
|
maxBudgetUsd: maxBudget,
|
|
391
406
|
agentSlug,
|
|
392
407
|
ownerName,
|
|
408
|
+
predictable,
|
|
393
409
|
};
|
|
394
410
|
}
|
|
395
411
|
/**
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -6237,7 +6237,7 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6237
6237
|
// ── CRON CRUD routes (continued) ──────────────────────────────
|
|
6238
6238
|
app.post('/api/cron', (req, res) => {
|
|
6239
6239
|
try {
|
|
6240
|
-
const { name, schedule, prompt, tier, enabled, work_dir, mode, max_hours, max_retries, after, agent, context, skills, allowedTools, allowedMcpServers, tags, category, } = req.body;
|
|
6240
|
+
const { name, schedule, prompt, tier, enabled, work_dir, mode, max_hours, max_retries, after, agent, context, skills, allowedTools, allowedMcpServers, tags, category, predictable, } = req.body;
|
|
6241
6241
|
if (!name || !schedule || !prompt) {
|
|
6242
6242
|
res.status(400).json({ error: 'name, schedule, and prompt are required' });
|
|
6243
6243
|
return;
|
|
@@ -6293,6 +6293,9 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6293
6293
|
if (typeof category === 'string' && category.trim()) {
|
|
6294
6294
|
job.category = category.trim().slice(0, 64);
|
|
6295
6295
|
}
|
|
6296
|
+
// Predictable mode — default to true (contract execution) for new
|
|
6297
|
+
// tricks created via the dashboard. Mirror the MCP tool default.
|
|
6298
|
+
job.predictable = (predictable === false) ? false : true;
|
|
6296
6299
|
jobs.push(job);
|
|
6297
6300
|
writeCronFileAt(cronFile, parsed, jobs);
|
|
6298
6301
|
res.json({ ok: true, message: `Created cron job: ${name}` });
|
|
@@ -6431,6 +6434,9 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6431
6434
|
delete jobs[idx].category;
|
|
6432
6435
|
}
|
|
6433
6436
|
}
|
|
6437
|
+
if (updates.predictable !== undefined) {
|
|
6438
|
+
jobs[idx].predictable = Boolean(updates.predictable);
|
|
6439
|
+
}
|
|
6434
6440
|
if (updates.name !== undefined && updates.name !== bareJobName) {
|
|
6435
6441
|
// Rename — check for duplicates
|
|
6436
6442
|
const dup = jobs.find((j, i) => i !== idx && String(j.name ?? '').toLowerCase() === String(updates.name).toLowerCase());
|
|
@@ -6548,6 +6554,7 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6548
6554
|
pinnedSkills: job.skills,
|
|
6549
6555
|
allowedTools: job.allowedTools,
|
|
6550
6556
|
allowedMcpServers: job.allowedMcpServers,
|
|
6557
|
+
predictable: job.predictable,
|
|
6551
6558
|
});
|
|
6552
6559
|
// Enrich each applied skill with its title/description/full markdown
|
|
6553
6560
|
// body so the UI can render "what the agent will actually read".
|
|
@@ -6586,7 +6593,9 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
6586
6593
|
mode: job.mode ?? null,
|
|
6587
6594
|
tags: job.tags ?? [],
|
|
6588
6595
|
category: job.category ?? null,
|
|
6596
|
+
predictable: typeof job.predictable === 'boolean' ? job.predictable : null,
|
|
6589
6597
|
},
|
|
6598
|
+
predictable: plan.predictable,
|
|
6590
6599
|
profile: profile ? { slug: profile.slug, name: profile.name } : null,
|
|
6591
6600
|
builtPrompt: plan.builtPrompt,
|
|
6592
6601
|
contextBlocks: plan.contextBlocks,
|
|
@@ -15926,6 +15935,25 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15926
15935
|
|
|
15927
15936
|
<!-- ═══ Build Page — Routines (single unified surface) ═══ -->
|
|
15928
15937
|
<div class="page" id="page-build">
|
|
15938
|
+
<!-- Build sub-tabs: Scheduled Tasks (single-prompt cron jobs) | Tricks (multi-step workflows) -->
|
|
15939
|
+
<div id="build-tabs" style="display:flex;gap:4px;padding:8px 18px 0;background:var(--bg-secondary);border-bottom:1px solid var(--border);flex-shrink:0">
|
|
15940
|
+
<button class="build-tab-btn active" data-build-tab="crons" onclick="switchBuildTab('crons')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-primary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
|
|
15941
|
+
<span style="margin-right:6px">📅</span>Scheduled Tasks <span id="build-tab-cron-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
|
|
15942
|
+
</button>
|
|
15943
|
+
<button class="build-tab-btn" data-build-tab="workflows" onclick="switchBuildTab('workflows')" style="padding:8px 14px;border-radius:6px 6px 0 0;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent">
|
|
15944
|
+
<span style="margin-right:6px">🔧</span>Tricks (workflows) <span id="build-tab-workflows-count" style="display:none;margin-left:4px;font-size:10px;background:var(--bg-tertiary);padding:1px 6px;border-radius:999px;color:var(--text-muted)">0</span>
|
|
15945
|
+
</button>
|
|
15946
|
+
</div>
|
|
15947
|
+
<style>
|
|
15948
|
+
.build-tab-btn.active { color:var(--accent) !important; border-bottom-color:var(--accent) !important; background:var(--bg-primary) !important; }
|
|
15949
|
+
.build-tab-btn:hover:not(.active) { color:var(--text-primary); }
|
|
15950
|
+
</style>
|
|
15951
|
+
<!-- Scheduled Tasks tab — populated by refreshCron() ─────────────────────── -->
|
|
15952
|
+
<div id="build-tab-crons" style="display:none;flex:1;min-height:0;overflow-y:auto;padding:18px;background:var(--bg-primary)">
|
|
15953
|
+
<div id="panel-cron"><div class="empty-state" style="padding:24px;color:var(--text-muted)">Loading scheduled tasks…</div></div>
|
|
15954
|
+
</div>
|
|
15955
|
+
<!-- Tricks (workflows) tab — existing RoutinesUI surface ─────────────────── -->
|
|
15956
|
+
<div id="build-tab-workflows" style="display:none;flex:1;min-height:0;display:flex;flex-direction:column">
|
|
15929
15957
|
<!-- Toolbar -->
|
|
15930
15958
|
<div id="routines-toolbar" style="display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--border);background:var(--bg-secondary);flex-wrap:wrap">
|
|
15931
15959
|
<h2 style="margin:0;font-size:18px;font-weight:600;color:var(--text-primary);display:flex;align-items:center;gap:8px"><span data-icon="workflow" class="icon-slot"></span> Tricks</h2>
|
|
@@ -16922,22 +16950,39 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
16922
16950
|
},
|
|
16923
16951
|
};
|
|
16924
16952
|
window.RoutinesUI = R;
|
|
16925
|
-
//
|
|
16926
|
-
//
|
|
16927
|
-
// and
|
|
16928
|
-
|
|
16929
|
-
|
|
16930
|
-
|
|
16931
|
-
|
|
16953
|
+
// Real switchBuildTab — toggles between Scheduled Tasks (cron) and
|
|
16954
|
+
// Tricks (workflows / RoutinesUI) sub-panes. Called from KPI tiles,
|
|
16955
|
+
// getting-started cards, and navigateTo('build', { tab: 'crons' }).
|
|
16956
|
+
// Default tab is 'crons' so users land on the scheduled-task surface
|
|
16957
|
+
// (with the capability strip + Preview) which is what most users want.
|
|
16958
|
+
window.switchBuildTab = function(tab) {
|
|
16959
|
+
var which = (tab === 'workflows') ? 'workflows' : 'crons';
|
|
16960
|
+
var crons = document.getElementById('build-tab-crons');
|
|
16961
|
+
var work = document.getElementById('build-tab-workflows');
|
|
16962
|
+
if (crons) crons.style.display = (which === 'crons') ? '' : 'none';
|
|
16963
|
+
if (work) work.style.display = (which === 'workflows') ? 'flex' : 'none';
|
|
16964
|
+
document.querySelectorAll('.build-tab-btn').forEach(function(b) {
|
|
16965
|
+
b.classList.toggle('active', b.getAttribute('data-build-tab') === which);
|
|
16966
|
+
});
|
|
16967
|
+
if (which === 'crons') {
|
|
16968
|
+
try { if (typeof refreshCron === 'function') refreshCron(); } catch (e) { /* */ }
|
|
16969
|
+
} else {
|
|
16970
|
+
try { R.init(); } catch (e) { /* */ }
|
|
16971
|
+
}
|
|
16972
|
+
};
|
|
16973
|
+
// Auto-init when the user lands on the build page (default to Scheduled Tasks).
|
|
16932
16974
|
document.addEventListener('DOMContentLoaded', function() {
|
|
16933
16975
|
var nav = document.querySelector('[data-page="build"]');
|
|
16934
|
-
if (nav) nav.addEventListener('click', function() {
|
|
16976
|
+
if (nav) nav.addEventListener('click', function() {
|
|
16977
|
+
setTimeout(function() { window.switchBuildTab('crons'); }, 50);
|
|
16978
|
+
});
|
|
16935
16979
|
// If page-build is already active on load (deep-link), init now.
|
|
16936
16980
|
var page = document.getElementById('page-build');
|
|
16937
|
-
if (page && page.classList.contains('active'))
|
|
16981
|
+
if (page && page.classList.contains('active')) window.switchBuildTab('crons');
|
|
16938
16982
|
});
|
|
16939
16983
|
})();
|
|
16940
16984
|
</script>
|
|
16985
|
+
</div><!-- /build-tab-workflows wrapper for Tricks (RoutinesUI) sub-pane -->
|
|
16941
16986
|
|
|
16942
16987
|
<!-- page-agent-detail merged into Team page; click an agent in Roster to drill down. -->
|
|
16943
16988
|
|
|
@@ -19637,6 +19682,19 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
19637
19682
|
<div class="form-group">
|
|
19638
19683
|
<label class="form-label">Capabilities <span style="color:var(--text-muted);font-weight:normal">(optional — pin skills + scope tools/MCP)</span></label>
|
|
19639
19684
|
<div class="form-hint" style="margin-bottom:6px">Pin learned procedures and constrain which tools / MCP servers this trick can use. Empty = inherit defaults. Use Preview on the card to see exactly what gets sent.</div>
|
|
19685
|
+
<div class="cap-section">
|
|
19686
|
+
<label class="cap-section-label">Predictable Mode</label>
|
|
19687
|
+
<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;font-size:12px;color:var(--text-primary)">
|
|
19688
|
+
<input type="checkbox" id="cron-predictable" checked style="margin-top:3px">
|
|
19689
|
+
<span>
|
|
19690
|
+
<strong>Run with only what's attached</strong> (recommended)
|
|
19691
|
+
<div style="font-size:11px;color:var(--text-muted);margin-top:3px;line-height:1.5">
|
|
19692
|
+
ON: MEMORY.md, team comms, delegation queue, and auto-matched skills are SKIPPED at fire-time. The trick runs ONLY with the prompt + pinned skills + tools you see here. No drift, no surprise.<br>
|
|
19693
|
+
OFF: legacy mode — runner injects MEMORY.md and other live context. Use only when the trick legitimately needs to re-read memory each fire (e.g. "summarize yesterday's daily note").
|
|
19694
|
+
</div>
|
|
19695
|
+
</span>
|
|
19696
|
+
</label>
|
|
19697
|
+
</div>
|
|
19640
19698
|
<div class="cap-section">
|
|
19641
19699
|
<label class="cap-section-label">Pinned Skills</label>
|
|
19642
19700
|
<div class="cap-picker-chips" id="cron-skills-chips"></div>
|
|
@@ -22690,6 +22748,8 @@ function renderScheduledTaskCard(task) {
|
|
|
22690
22748
|
var badges = '';
|
|
22691
22749
|
if (task.owner) badges += '<span class="badge badge-orange">' + esc(task.owner) + '</span>';
|
|
22692
22750
|
if (task.category) badges += '<span class="badge badge-gray" title="Category">' + esc(task.category) + '</span>';
|
|
22751
|
+
if (task.predictable === true) badges += '<span class="badge badge-green" title="Contract mode — runs with only the prompt + pinned skills/tools. No MEMORY.md, no auto-matched skills, no team comms injection at fire-time.">🔒 predictable</span>';
|
|
22752
|
+
else if (task.predictable === false) badges += '<span class="badge badge-yellow" title="Dynamic mode — fire-time injects MEMORY.md, recent team activity, and auto-matched skills. Can drift from chat-time intent.">🔄 reads memory</span>';
|
|
22693
22753
|
if (task.mode === 'unleashed') badges += '<span class="badge badge-purple">long-running</span>';
|
|
22694
22754
|
if (task.after) badges += '<span class="badge badge-yellow" title="Triggered after ' + esc(task.after) + '">after ' + esc(task.after) + '</span>';
|
|
22695
22755
|
if (task.maxRetries != null) badges += '<span class="badge badge-gray">' + esc(task.maxRetries) + ' retries</span>';
|
|
@@ -23753,6 +23813,9 @@ function resetTrickCapabilityState() {
|
|
|
23753
23813
|
if (toolsToggle) toolsToggle.textContent = '▾ Show';
|
|
23754
23814
|
var catEl = document.getElementById('cron-category');
|
|
23755
23815
|
if (catEl) catEl.value = '';
|
|
23816
|
+
// Default: predictable ON for new tricks (matches add_cron_job default).
|
|
23817
|
+
var predEl = document.getElementById('cron-predictable');
|
|
23818
|
+
if (predEl) predEl.checked = true;
|
|
23756
23819
|
renderSkillsPickerChips();
|
|
23757
23820
|
renderMcpPickerChips();
|
|
23758
23821
|
renderTagsPickerChips();
|
|
@@ -24066,6 +24129,10 @@ function openEditCronModal(jobName) {
|
|
|
24066
24129
|
if (allowedTools.length > 0) toggleAllowedToolsPanel();
|
|
24067
24130
|
var catEl = document.getElementById('cron-category');
|
|
24068
24131
|
if (catEl) catEl.value = job.category || '';
|
|
24132
|
+
// Predictable: respect saved value; if undefined (legacy trick), keep
|
|
24133
|
+
// unchecked so we don't silently change runner behavior.
|
|
24134
|
+
var predEl = document.getElementById('cron-predictable');
|
|
24135
|
+
if (predEl) predEl.checked = (job.predictable === true);
|
|
24069
24136
|
renderSkillsPickerChips();
|
|
24070
24137
|
renderMcpPickerChips();
|
|
24071
24138
|
renderTagsPickerChips();
|
|
@@ -24104,7 +24171,23 @@ function closeCronPreviewModal() {
|
|
|
24104
24171
|
|
|
24105
24172
|
function renderCronPreview(d) {
|
|
24106
24173
|
var html = '';
|
|
24107
|
-
//
|
|
24174
|
+
// Predictable verdict line — the headline visibility win.
|
|
24175
|
+
html += '<div class="preview-section">';
|
|
24176
|
+
if (d.predictable === true) {
|
|
24177
|
+
html += '<div style="padding:10px 12px;border-radius:6px;background:rgba(16,185,129,0.12);color:var(--green);font-size:13px;font-weight:500">'
|
|
24178
|
+
+ '🔒 <strong>Predictable</strong> — what you see here is exactly what will run. No MEMORY.md, no team comms, no auto-matched skills injected at fire-time.'
|
|
24179
|
+
+ '</div>';
|
|
24180
|
+
} else if (d.predictable === false) {
|
|
24181
|
+
html += '<div style="padding:10px 12px;border-radius:6px;background:rgba(245,158,11,0.12);color:var(--yellow);font-size:13px;font-weight:500">'
|
|
24182
|
+
+ '⚠ <strong>Reads memory at fire-time</strong> — fire-time will ALSO inject MEMORY.md, recent team comms, delegation queue, and auto-matched skills. Output may differ from this preview if those drift between now and fire.'
|
|
24183
|
+
+ '</div>';
|
|
24184
|
+
} else {
|
|
24185
|
+
html += '<div style="padding:10px 12px;border-radius:6px;background:var(--bg-tertiary);color:var(--text-muted);font-size:12px">'
|
|
24186
|
+
+ 'Legacy trick — predictable mode not set. Runs in dynamic mode (injects MEMORY.md, etc). Edit and turn on Predictable Mode to lock down behavior.'
|
|
24187
|
+
+ '</div>';
|
|
24188
|
+
}
|
|
24189
|
+
html += '</div>';
|
|
24190
|
+
// Warnings band
|
|
24108
24191
|
if (Array.isArray(d.warnings) && d.warnings.length > 0) {
|
|
24109
24192
|
html += '<div class="preview-section">';
|
|
24110
24193
|
for (var w = 0; w < d.warnings.length; w++) {
|
|
@@ -24305,6 +24388,7 @@ async function saveCronJob() {
|
|
|
24305
24388
|
const categoryRaw = (document.getElementById('cron-category')?.value || '').trim();
|
|
24306
24389
|
const category = categoryRaw || undefined;
|
|
24307
24390
|
const allowedTools = parseAllowedToolsRaw();
|
|
24391
|
+
const predictable = !!document.getElementById('cron-predictable')?.checked;
|
|
24308
24392
|
|
|
24309
24393
|
if (!name || !schedule || !prompt) {
|
|
24310
24394
|
toast('Please fill in all fields', 'error');
|
|
@@ -24327,6 +24411,7 @@ async function saveCronJob() {
|
|
|
24327
24411
|
: (_cronSelectedMcp.length ? _cronSelectedMcp : undefined),
|
|
24328
24412
|
tags: editingCronJob ? _cronTags : (_cronTags.length ? _cronTags : undefined),
|
|
24329
24413
|
category: editingCronJob ? (category || '') : category,
|
|
24414
|
+
predictable,
|
|
24330
24415
|
};
|
|
24331
24416
|
|
|
24332
24417
|
if (editingCronJob) {
|
|
@@ -97,6 +97,10 @@ export interface ScheduledTaskCard {
|
|
|
97
97
|
tags?: string[];
|
|
98
98
|
/** Optional category bucket. */
|
|
99
99
|
category?: string;
|
|
100
|
+
/** Predictable (contract) mode — true means runner skips MEMORY.md /
|
|
101
|
+
* team comms / auto-matched skills. The visibility-on-card flag for
|
|
102
|
+
* "this trick will run with only what you see here." */
|
|
103
|
+
predictable?: boolean;
|
|
100
104
|
}
|
|
101
105
|
export interface ScheduledWorkflowCard {
|
|
102
106
|
type: 'scheduled_workflow';
|
|
@@ -215,6 +215,7 @@ export function buildOperationsSnapshot(input) {
|
|
|
215
215
|
allowedMcpServers: asStringArray(job.allowed_mcp_servers ?? job.allowedMcpServers),
|
|
216
216
|
tags: asStringArray(job.tags),
|
|
217
217
|
category: typeof job.category === 'string' && job.category.trim() ? job.category.trim() : undefined,
|
|
218
|
+
predictable: typeof job.predictable === 'boolean' ? job.predictable : undefined,
|
|
218
219
|
};
|
|
219
220
|
}).sort((a, b) => a.owner.localeCompare(b.owner) || a.displayName.localeCompare(b.displayName));
|
|
220
221
|
const scheduledWorkflows = input.workflowSummaries
|
|
@@ -132,6 +132,8 @@ export function parseCronJobs() {
|
|
|
132
132
|
const category = typeof categoryRaw === 'string' && categoryRaw.trim()
|
|
133
133
|
? categoryRaw.trim().slice(0, 64)
|
|
134
134
|
: undefined;
|
|
135
|
+
// Predictable (contract) mode — undefined means legacy behavior.
|
|
136
|
+
const predictable = typeof job.predictable === 'boolean' ? job.predictable : undefined;
|
|
135
137
|
if (!name || !schedule || !prompt) {
|
|
136
138
|
logger.warn({ job }, 'Skipping malformed cron job');
|
|
137
139
|
continue;
|
|
@@ -139,7 +141,7 @@ export function parseCronJobs() {
|
|
|
139
141
|
jobs.push({
|
|
140
142
|
name, schedule, prompt, enabled, tier, maxTurns, model, workDir, mode,
|
|
141
143
|
maxHours, maxRetries, after, successCriteria, alwaysDeliver, context, preCheck, agentSlug,
|
|
142
|
-
skills, allowedTools, allowedMcpServers, tags, category,
|
|
144
|
+
skills, allowedTools, allowedMcpServers, tags, category, predictable,
|
|
143
145
|
});
|
|
144
146
|
}
|
|
145
147
|
return jobs;
|
|
@@ -199,6 +201,7 @@ export function parseAgentCronJobs(agentsDir) {
|
|
|
199
201
|
const category = typeof categoryRaw === 'string' && categoryRaw.trim()
|
|
200
202
|
? categoryRaw.trim().slice(0, 64)
|
|
201
203
|
: undefined;
|
|
204
|
+
const predictable = typeof job.predictable === 'boolean' ? job.predictable : undefined;
|
|
202
205
|
if (!name || !schedule || !prompt) {
|
|
203
206
|
logger.warn({ job, agent: slug }, 'Skipping malformed agent cron job');
|
|
204
207
|
continue;
|
|
@@ -209,7 +212,7 @@ export function parseAgentCronJobs(agentsDir) {
|
|
|
209
212
|
schedule, prompt, enabled, tier, maxTurns, model, workDir,
|
|
210
213
|
mode, maxHours, maxRetries, after, successCriteria, context, preCheck,
|
|
211
214
|
agentSlug: slug,
|
|
212
|
-
skills, allowedTools, allowedMcpServers, tags, category,
|
|
215
|
+
skills, allowedTools, allowedMcpServers, tags, category, predictable,
|
|
213
216
|
});
|
|
214
217
|
}
|
|
215
218
|
}
|
|
@@ -1094,12 +1097,12 @@ export class CronScheduler {
|
|
|
1094
1097
|
const startedAt = new Date();
|
|
1095
1098
|
try {
|
|
1096
1099
|
// Standard cron jobs get a timeout via SDK AbortController (advisor may override)
|
|
1097
|
-
let response = await this.gateway.handleCronJob(job.name, jobPrompt, job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours, effectiveTimeoutMs, job.successCriteria, job.agentSlug, job.skills, job.allowedTools, job.allowedMcpServers);
|
|
1100
|
+
let response = await this.gateway.handleCronJob(job.name, jobPrompt, job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours, effectiveTimeoutMs, job.successCriteria, job.agentSlug, job.skills, job.allowedTools, job.allowedMcpServers, job.predictable);
|
|
1098
1101
|
// alwaysDeliver: retry once if the response is empty/noise
|
|
1099
1102
|
if (job.alwaysDeliver && (!response || CronScheduler.isCronNoise(response))) {
|
|
1100
1103
|
logger.info({ job: job.name }, 'alwaysDeliver: empty/noise response — retrying once');
|
|
1101
1104
|
try {
|
|
1102
|
-
const retryResponse = await this.gateway.handleCronJob(job.name, jobPrompt + '\n\nYou MUST produce a brief status update. Do NOT return __NOTHING__.', job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours, effectiveTimeoutMs, job.successCriteria, job.agentSlug, job.skills, job.allowedTools, job.allowedMcpServers);
|
|
1105
|
+
const retryResponse = await this.gateway.handleCronJob(job.name, jobPrompt + '\n\nYou MUST produce a brief status update. Do NOT return __NOTHING__.', job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours, effectiveTimeoutMs, job.successCriteria, job.agentSlug, job.skills, job.allowedTools, job.allowedMcpServers, job.predictable);
|
|
1103
1106
|
if (retryResponse && !CronScheduler.isCronNoise(retryResponse)) {
|
|
1104
1107
|
response = retryResponse;
|
|
1105
1108
|
}
|
package/dist/gateway/router.d.ts
CHANGED
|
@@ -175,7 +175,9 @@ export declare class Gateway {
|
|
|
175
175
|
handleCronJob(jobName: string, jobPrompt: string, tier?: number, maxTurns?: number, model?: string, workDir?: string,
|
|
176
176
|
/** Accepted for back-compat; canonical SDK path executes every job
|
|
177
177
|
* identically. Affects only UI display + budget heuristics elsewhere. */
|
|
178
|
-
_mode?: 'standard' | 'unleashed', maxHours?: number, timeoutMs?: number, successCriteria?: string[], agentSlug?: string, pinnedSkills?: string[], allowedTools?: string[], allowedMcpServers?: string[]
|
|
178
|
+
_mode?: 'standard' | 'unleashed', maxHours?: number, timeoutMs?: number, successCriteria?: string[], agentSlug?: string, pinnedSkills?: string[], allowedTools?: string[], allowedMcpServers?: string[],
|
|
179
|
+
/** Predictable (contract) mode — runner skips memory/team/auto-skills. */
|
|
180
|
+
predictable?: boolean): Promise<string>;
|
|
179
181
|
/**
|
|
180
182
|
* Process a team message as an autonomous task — same multi-phase execution
|
|
181
183
|
* as cron unleashed jobs, so agents can work until done instead of being
|
package/dist/gateway/router.js
CHANGED
|
@@ -1969,7 +1969,9 @@ export class Gateway {
|
|
|
1969
1969
|
* identically. Affects only UI display + budget heuristics elsewhere. */
|
|
1970
1970
|
_mode, maxHours, timeoutMs, successCriteria, agentSlug,
|
|
1971
1971
|
// ── Trick capabilities (optional; preserve today's behavior when omitted) ─
|
|
1972
|
-
pinnedSkills, allowedTools, allowedMcpServers
|
|
1972
|
+
pinnedSkills, allowedTools, allowedMcpServers,
|
|
1973
|
+
/** Predictable (contract) mode — runner skips memory/team/auto-skills. */
|
|
1974
|
+
predictable) {
|
|
1973
1975
|
const releaseLane = await lanes.acquire('cron');
|
|
1974
1976
|
// Build a wall-clock abort timer from maxHours / timeoutMs.
|
|
1975
1977
|
// Whichever is shorter wins. Defaults to 1h if neither is set.
|
|
@@ -2010,6 +2012,7 @@ export class Gateway {
|
|
|
2010
2012
|
pinnedSkills,
|
|
2011
2013
|
allowedTools,
|
|
2012
2014
|
allowedMcpServers,
|
|
2015
|
+
predictable,
|
|
2013
2016
|
});
|
|
2014
2017
|
scanner.refreshIntegrity();
|
|
2015
2018
|
// Stash trick-capability metadata for the scheduler to read when
|
|
@@ -1025,20 +1025,21 @@ export function registerAdminTools(server) {
|
|
|
1025
1025
|
return textResult(lines.join('\n\n'));
|
|
1026
1026
|
});
|
|
1027
1027
|
// ── Add Cron Job ────────────────────────────────────────────────────────
|
|
1028
|
-
server.tool('add_cron_job', 'Add a new scheduled
|
|
1028
|
+
server.tool('add_cron_job', 'Add a new scheduled task. ⚠ BEFORE CALLING THIS TOOL: propose the concrete plan to the user in chat and get explicit approval. The `prompt` you save should be SELF-CONTAINED — list the actual recipients, the actual template/content, the actual criteria. AVOID vague references like "recent leads" or "this week\'s items" that the trick will re-derive at fire-time, because re-derivation reads from MEMORY.md which drifts between chat-time agreement and fire-time execution. Good prompt: "Send template `monday-followup` to alice@x.com, bob@y.com, carol@z.com." Bad prompt: "Send follow-up to recent leads." The default `predictable: true` mode runs the trick with ONLY the prompt + explicitly-attached skills/tools — no MEMORY.md, no team-comms injection, no runtime skill auto-match. Set `predictable: false` ONLY if the user explicitly wants a dynamic trick that re-resolves data each fire (e.g., "summarize yesterday\'s daily note" where the data legitimately changes).', {
|
|
1029
1029
|
name: z.string().describe('Job name (unique identifier)'),
|
|
1030
1030
|
schedule: z.string().describe('Cron expression (e.g., "0 9 * * 1" for Monday 9 AM)'),
|
|
1031
|
-
prompt: z.string().describe('The prompt/instruction for the assistant to execute'),
|
|
1031
|
+
prompt: z.string().describe('The prompt/instruction for the assistant to execute. SHOULD BE CONCRETE — list actual recipients, criteria, content. Vague prompts re-derive at fire-time and cause "agent agreed in chat but emailed wrong people" failures.'),
|
|
1032
1032
|
tier: z.number().optional().default(1).describe('Security tier (1=auto, 2=logged, 3=approval). Tier 2+ also raises the per-run budget cap.'),
|
|
1033
1033
|
enabled: z.boolean().optional().default(true).describe('Whether the job is enabled'),
|
|
1034
1034
|
work_dir: z.string().optional().describe('Project directory to run in (agent gets access to project tools, CLAUDE.md, files)'),
|
|
1035
1035
|
max_hours: z.number().optional().describe('Wall-clock cap in hours. Defaults to 1h. Run aborts via AbortSignal when exceeded.'),
|
|
1036
|
-
|
|
1036
|
+
predictable: z.boolean().optional().default(true).describe('PREDICTABLE MODE (default true, recommended). When true, the runner runs with ONLY the prompt + pinned skills + criteria + linked goals + prior progress — MEMORY.md, team comms, delegation queue, and runtime skill auto-match are SKIPPED. This is the contract model: trick executes the plan you saved, not whatever memory says today. Set to false only when the user EXPLICITLY needs dynamic behavior — and tell them what that means.'),
|
|
1037
|
+
skills: z.array(z.string()).optional().describe('Pinned skill slugs (filename minus .md, slashes flattened to dashes). Loaded BEFORE runtime auto-match. Total injected per run capped at 4. In predictable mode, ONLY pinned skills load (no auto-match).'),
|
|
1037
1038
|
allowed_tools: z.array(z.string()).optional().describe('Per-trick tool whitelist. When set, intersected with the agent profile allowlist. Agent is always force-included for sub-agent delegation. Empty/omitted inherits from profile.'),
|
|
1038
1039
|
allowed_mcp_servers: z.array(z.string()).optional().describe('Per-trick MCP server whitelist (server names from list_mcp_servers). Applied AFTER profile allowlist. Empty/omitted inherits from profile.'),
|
|
1039
1040
|
tags: z.array(z.string()).optional().describe('Free-form tags for grouping/filtering in the dashboard.'),
|
|
1040
1041
|
category: z.string().optional().describe('Single category bucket (e.g. "ops", "research").'),
|
|
1041
|
-
}, async ({ name: jobName, schedule, prompt, tier, enabled, work_dir, max_hours, skills, allowed_tools, allowed_mcp_servers, tags, category }) => {
|
|
1042
|
+
}, async ({ name: jobName, schedule, prompt, tier, enabled, work_dir, max_hours, predictable, skills, allowed_tools, allowed_mcp_servers, tags, category }) => {
|
|
1042
1043
|
// Validate cron expression
|
|
1043
1044
|
const cronMod = await import('node-cron');
|
|
1044
1045
|
if (!cronMod.default.validate(schedule)) {
|
|
@@ -1076,6 +1077,9 @@ export function registerAdminTools(server) {
|
|
|
1076
1077
|
newJob.work_dir = work_dir;
|
|
1077
1078
|
if (max_hours)
|
|
1078
1079
|
newJob.max_hours = max_hours;
|
|
1080
|
+
// Predictable mode: persist explicitly so behavior is locked. Default
|
|
1081
|
+
// for new chat-created tricks is true (contract execution).
|
|
1082
|
+
newJob.predictable = predictable !== false;
|
|
1079
1083
|
// ── Trick capabilities (snake_case YAML keys) ──────────────────
|
|
1080
1084
|
if (Array.isArray(skills) && skills.length)
|
|
1081
1085
|
newJob.skills = skills.map(s => String(s).trim()).filter(Boolean);
|
|
@@ -1121,6 +1125,7 @@ export function registerAdminTools(server) {
|
|
|
1121
1125
|
details.push(` Project: ${work_dir}`);
|
|
1122
1126
|
if (max_hours)
|
|
1123
1127
|
details.push(` Wall-clock cap: ${max_hours}h`);
|
|
1128
|
+
details.push(` Predictable mode: ${newJob.predictable ? 'ON — runs with only the prompt + pinned skills/tools (no MEMORY.md drift)' : 'OFF — runs with MEMORY.md + auto-matched skills (dynamic, may surprise)'}`);
|
|
1124
1129
|
if (Array.isArray(skills) && skills.length)
|
|
1125
1130
|
details.push(` Pinned skills: ${skills.join(', ')}`);
|
|
1126
1131
|
if (Array.isArray(allowed_tools) && allowed_tools.length)
|
|
@@ -1138,7 +1143,7 @@ export function registerAdminTools(server) {
|
|
|
1138
1143
|
return textResult(`Added cron job "${jobName}":\n${details.join('\n')}\n\n${verifyMsg}${goalHint}`);
|
|
1139
1144
|
});
|
|
1140
1145
|
// ── Update Cron Job ─────────────────────────────────────────────────────
|
|
1141
|
-
server.tool('update_cron_job', 'Update an existing cron job in CRON.md. Partial — only fields you supply change. To CLEAR a capability allowlist (skills/allowed_tools/allowed_mcp_servers/tags), pass an empty array. To clear category, pass an empty string. The daemon auto-reloads on file change. Use preview_cron_job to confirm what will run before the next fire.', {
|
|
1146
|
+
server.tool('update_cron_job', 'Update an existing cron job in CRON.md. Partial — only fields you supply change. To CLEAR a capability allowlist (skills/allowed_tools/allowed_mcp_servers/tags), pass an empty array. To clear category, pass an empty string. The daemon auto-reloads on file change. Use preview_cron_job to confirm what will run before the next fire. ⚠ Flipping `predictable` from true to false changes whether the trick reads MEMORY.md at fire-time — make sure the user understands the tradeoff before you toggle it.', {
|
|
1142
1147
|
name: z.string().describe('Existing job name to update.'),
|
|
1143
1148
|
schedule: z.string().optional().describe('New cron expression.'),
|
|
1144
1149
|
prompt: z.string().optional().describe('New prompt.'),
|
|
@@ -1146,12 +1151,13 @@ export function registerAdminTools(server) {
|
|
|
1146
1151
|
enabled: z.boolean().optional().describe('Enable/disable.'),
|
|
1147
1152
|
work_dir: z.string().optional().describe('Project directory. Empty string clears.'),
|
|
1148
1153
|
max_hours: z.number().optional().describe('Wall-clock cap in hours.'),
|
|
1154
|
+
predictable: z.boolean().optional().describe('Predictable (contract) mode. true = runner skips MEMORY.md / team comms / auto-matched skills, runs ONLY with the prompt + pinned items. false = legacy injects-everything mode (memory drift risk). Tell the user what they\'re opting into when flipping this.'),
|
|
1149
1155
|
skills: z.array(z.string()).optional().describe('Pinned skill slugs. Empty array clears.'),
|
|
1150
1156
|
allowed_tools: z.array(z.string()).optional().describe('Tool allowlist. Empty array clears.'),
|
|
1151
1157
|
allowed_mcp_servers: z.array(z.string()).optional().describe('MCP allowlist. Empty array clears.'),
|
|
1152
1158
|
tags: z.array(z.string()).optional().describe('Tags. Empty array clears.'),
|
|
1153
1159
|
category: z.string().optional().describe('Category bucket. Empty string clears.'),
|
|
1154
|
-
}, async ({ name: jobName, schedule, prompt, tier, enabled, work_dir, max_hours, skills, allowed_tools, allowed_mcp_servers, tags, category }) => {
|
|
1160
|
+
}, async ({ name: jobName, schedule, prompt, tier, enabled, work_dir, max_hours, predictable, skills, allowed_tools, allowed_mcp_servers, tags, category }) => {
|
|
1155
1161
|
if (!existsSync(CRON_FILE)) {
|
|
1156
1162
|
return textResult('CRON.md not found. Use add_cron_job to create one first.');
|
|
1157
1163
|
}
|
|
@@ -1202,6 +1208,10 @@ export function registerAdminTools(server) {
|
|
|
1202
1208
|
job.max_hours = max_hours;
|
|
1203
1209
|
changed.push(`max_hours → ${max_hours}`);
|
|
1204
1210
|
}
|
|
1211
|
+
if (predictable !== undefined) {
|
|
1212
|
+
job.predictable = predictable;
|
|
1213
|
+
changed.push(`predictable → ${predictable ? 'ON (contract mode — only what\'s attached)' : 'OFF (dynamic — reads MEMORY.md, may drift)'}`);
|
|
1214
|
+
}
|
|
1205
1215
|
// ── Capabilities — empty array CLEARS, omitted leaves alone ────
|
|
1206
1216
|
if (skills !== undefined) {
|
|
1207
1217
|
if (skills.length) {
|
|
@@ -1298,11 +1308,20 @@ export function registerAdminTools(server) {
|
|
|
1298
1308
|
pinnedSkills: job.skills,
|
|
1299
1309
|
allowedTools: job.allowedTools,
|
|
1300
1310
|
allowedMcpServers: job.allowedMcpServers,
|
|
1311
|
+
predictable: job.predictable,
|
|
1301
1312
|
});
|
|
1302
1313
|
const allServers = discoverMcpServers();
|
|
1303
1314
|
const lines = [];
|
|
1304
1315
|
lines.push(`# Preview: ${job.name}`);
|
|
1305
1316
|
lines.push('');
|
|
1317
|
+
// Verdict line — the headline visibility win for the user.
|
|
1318
|
+
if (plan.predictable) {
|
|
1319
|
+
lines.push(`✓ **Predictable** — what you see here is exactly what will run. No MEMORY.md, no team activity, no auto-matched skills injected at fire-time. Pure contract execution.`);
|
|
1320
|
+
}
|
|
1321
|
+
else {
|
|
1322
|
+
lines.push(`⚠ **Reads memory at fire-time** — this trick is in dynamic mode. At fire-time, the runner ALSO injects MEMORY.md, recent team comms, delegation queue, and auto-matched skills. The agent's output may differ from this preview if those have drifted since chat-time. Set \`predictable: true\` if that's not what you want.`);
|
|
1323
|
+
}
|
|
1324
|
+
lines.push('');
|
|
1306
1325
|
lines.push(`**Schedule:** ${job.schedule} **Tier:** ${plan.tier} (${plan.effort}${plan.maxBudgetUsd ? `, budget $${plan.maxBudgetUsd}` : ''})`);
|
|
1307
1326
|
if (job.agentSlug)
|
|
1308
1327
|
lines.push(`**Agent:** ${job.agentSlug}`);
|
package/dist/types.d.ts
CHANGED
|
@@ -354,6 +354,22 @@ export interface CronJobDefinition {
|
|
|
354
354
|
/** Single category bucket — convenience for default grouping in the
|
|
355
355
|
* dashboard (e.g. "ops", "research", "morning"). */
|
|
356
356
|
category?: string;
|
|
357
|
+
/**
|
|
358
|
+
* Predictable mode (the "contract" model) — runs the trick with ONLY
|
|
359
|
+
* the prompt + explicitly-attached skills/criteria/goals + tools. Skips
|
|
360
|
+
* MEMORY.md injection, auto-matched skills, team comms, and delegation
|
|
361
|
+
* queue. The fix for "agent said OK in chat then fired with stale memory."
|
|
362
|
+
*
|
|
363
|
+
* - undefined / false: legacy behavior — runner injects everything
|
|
364
|
+
* (MEMORY.md, auto-matched skills, team activity, delegation). What
|
|
365
|
+
* chat-style autonomous work needs, but contaminates scheduled tasks.
|
|
366
|
+
* - true: contract mode — runner only includes what was explicitly
|
|
367
|
+
* attached. The trick executes the plan you saw in chat, nothing more.
|
|
368
|
+
*
|
|
369
|
+
* `add_cron_job` defaults this to `true` for new chat-created tricks.
|
|
370
|
+
* Existing tricks (no field set) keep current behavior — backward compat.
|
|
371
|
+
*/
|
|
372
|
+
predictable?: boolean;
|
|
357
373
|
}
|
|
358
374
|
export type LongTaskRisk = 'normal' | 'long' | 'huge' | 'unsafe';
|
|
359
375
|
export type LongTaskRoute = 'standard' | 'checkpointed' | 'opus_1m' | 'sonnet_1m' | 'split_required';
|