mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-02 01:29:23 +00:00
feat: implement FullJob routine mode with scheduler dispatch (#288)
* feat: implement FullJob routine mode with scheduler dispatch FullJob routines previously fell back to lightweight mode (single LLM call, no tools) with a warning. This wires them to the existing Scheduler/Worker infrastructure so they dispatch real jobs with full tool access. Fire-and-forget model: the routine creates a job via ContextManager, schedules it, links the routine_run to the job_id, and completes immediately. The job runs independently with full tool access. - Add RoutineError::JobDispatchFailed variant - Add RoutineStore::link_routine_run_to_job (PostgreSQL + libSQL) - Add execute_full_job() in routine_engine with context_manager/scheduler - Wire context_manager + scheduler into RoutineEngine from agent_loop - Fix pre-existing clippy warnings in tests/html_to_markdown.rs Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: persist job to DB before scheduling in execute_full_job The worker emits job_actions and llm_calls rows that reference agent_jobs via foreign key. Without persisting the job first, those inserts can fail. Match the pattern from commands.rs: fetch JobContext, save_job(), then schedule. Co-Authored-By: Claude Opus 4.6 <[email protected]> * refactor: consolidate job dispatch into Scheduler::dispatch_job and wire max_iterations Move the create + persist + schedule sequence into a single Scheduler::dispatch_job() method so callers (commands.rs, routine_engine.rs) don't duplicate the logic. FullJob routines now pass max_iterations via job metadata, and the worker reads it (defaulting to 50 if unset). Also removes the context_manager field from RoutineEngine since dispatch_job handles everything internally. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: clamp max_iterations to 500 and log category update failures Address PR review feedback: - worker.rs: clamp max_iterations from metadata to MAX_WORKER_ITERATIONS (500) to prevent unbounded LLM token usage from malicious/buggy configs - commands.rs: log warning on category update failure instead of silently discarding the error Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: persist worker events to DB and fix activity tab rendering In-process Worker (used by Scheduler::dispatch_job) now persists events via save_job_event at key execution points: plan creation, LLM responses, tool_use, tool_result, and job completion/failure/stuck. Event data shapes match the container worker format so the gateway activity tab renders them correctly. Frontend: tool_result errors now show a red X icon with danger styling instead of a silent empty output. The result event falls back to the error field when message is absent. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ea57447649
commit
04d3b005b1
@@ -2187,14 +2187,20 @@ function appendActivityEvent(terminal, eventType, data) {
|
||||
+ escapeHtml(typeof data.input === 'string' ? data.input : JSON.stringify(data.input, null, 2))
|
||||
+ '</pre></details>';
|
||||
break;
|
||||
case 'tool_result':
|
||||
el.innerHTML = '<details class="activity-tool-block activity-tool-result"><summary>'
|
||||
+ '<span class="activity-tool-icon">✓</span> '
|
||||
case 'tool_result': {
|
||||
const trSuccess = data.success !== false;
|
||||
const trIcon = trSuccess ? '✓' : '✗';
|
||||
const trOutput = data.output || data.error || '';
|
||||
const trClass = 'activity-tool-block activity-tool-result'
|
||||
+ (trSuccess ? '' : ' activity-tool-error');
|
||||
el.innerHTML = '<details class="' + trClass + '"><summary>'
|
||||
+ '<span class="activity-tool-icon">' + trIcon + '</span> '
|
||||
+ escapeHtml(data.tool_name || 'result')
|
||||
+ '</summary><pre class="activity-tool-output">'
|
||||
+ escapeHtml(data.output || '')
|
||||
+ escapeHtml(trOutput)
|
||||
+ '</pre></details>';
|
||||
break;
|
||||
}
|
||||
case 'status':
|
||||
el.innerHTML = '<span class="activity-status">' + escapeHtml(data.message || '') + '</span>';
|
||||
break;
|
||||
@@ -2202,7 +2208,7 @@ function appendActivityEvent(terminal, eventType, data) {
|
||||
el.className += ' activity-final';
|
||||
const success = data.success !== false;
|
||||
el.innerHTML = '<span class="activity-result-status" data-success="' + success + '">'
|
||||
+ escapeHtml(data.message || data.status || 'done') + '</span>';
|
||||
+ escapeHtml(data.message || data.error || data.status || 'done') + '</span>';
|
||||
if (data.session_id) {
|
||||
el.innerHTML += ' <span class="activity-session-id">session: ' + escapeHtml(data.session_id) + '</span>';
|
||||
}
|
||||
@@ -2387,7 +2393,9 @@ function renderRoutineDetail(routine) {
|
||||
+ '<td>' + formatDate(run.started_at) + '</td>'
|
||||
+ '<td>' + formatDate(run.completed_at) + '</td>'
|
||||
+ '<td><span class="badge ' + runStatusClass + '">' + escapeHtml(run.status) + '</span></td>'
|
||||
+ '<td>' + escapeHtml(run.result_summary || '-') + '</td>'
|
||||
+ '<td>' + escapeHtml(run.result_summary || '-')
|
||||
+ (run.job_id ? ' <a href="#" onclick="event.preventDefault(); switchTab(\'jobs\'); openJobDetail(\'' + run.job_id + '\')">[view job]</a>' : '')
|
||||
+ '</td>'
|
||||
+ '<td>' + (run.tokens_used != null ? run.tokens_used : '-') + '</td>'
|
||||
+ '</tr>';
|
||||
}
|
||||
|
||||
@@ -2240,6 +2240,14 @@ body {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.activity-tool-error .activity-tool-icon {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.activity-tool-error summary {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.activity-tool-input,
|
||||
.activity-tool-output {
|
||||
padding: 8px 10px;
|
||||
|
||||
Reference in New Issue
Block a user