summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-08 20:16:00 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-08 20:16:00 +0000
commit1f36e2312d316969db65a601ac7d9793fbc3bc4c (patch)
tree1d91358beaf910df23a5bd18b9dabbc3f59d448a /web
parent9955a2f10c034dac60bc17cde6b80b432e21d9d3 (diff)
feat: rename working_dir→project_dir; git sandbox execution
- ClaudeConfig.WorkingDir → ProjectDir (json: project_dir) - UnmarshalJSON fallback reads legacy working_dir from DB records - New executions with project_dir clone into a temp sandbox via git clone --local - Non-git project_dirs get git init + initial commit before clone - After success: verify clean working tree, merge --ff-only back to project_dir, remove sandbox - On failure/BLOCKED: sandbox preserved, path included in error message - Resume executions run directly in project_dir (no re-clone) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web')
-rw-r--r--web/app.js38
-rw-r--r--web/index.html5
2 files changed, 23 insertions, 20 deletions
diff --git a/web/app.js b/web/app.js
index 3b807c4..28d438a 100644
--- a/web/app.js
+++ b/web/app.js
@@ -228,9 +228,10 @@ function sortTasksByDate(tasks) {
// ── Filter ────────────────────────────────────────────────────────────────────
-const HIDE_STATES = new Set(['COMPLETED', 'FAILED']);
-const ACTIVE_STATES = new Set(['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED']);
-const DONE_STATES = new Set(['COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']);
+const HIDE_STATES = new Set(['COMPLETED', 'FAILED']);
+const ACTIVE_STATES = new Set(['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED']);
+const INTERRUPTED_STATES = new Set(['CANCELLED', 'FAILED']);
+const DONE_STATES = new Set(['COMPLETED', 'TIMED_OUT', 'BUDGET_EXCEEDED']);
// filterActiveTasks uses its own set (excludes PENDING — tasks "in-flight" only)
const _PANEL_ACTIVE_STATES = new Set(['RUNNING', 'READY', 'QUEUED', 'BLOCKED']);
@@ -245,8 +246,9 @@ export function filterActiveTasks(tasks) {
}
export function filterTasksByTab(tasks, tab) {
- if (tab === 'active') return tasks.filter(t => ACTIVE_STATES.has(t.state));
- if (tab === 'done') return tasks.filter(t => DONE_STATES.has(t.state));
+ if (tab === 'active') return tasks.filter(t => ACTIVE_STATES.has(t.state));
+ if (tab === 'interrupted') return tasks.filter(t => INTERRUPTED_STATES.has(t.state));
+ if (tab === 'done') return tasks.filter(t => DONE_STATES.has(t.state));
return tasks;
}
@@ -517,7 +519,7 @@ function createEditForm(task) {
form.appendChild(makeField('Description', 'textarea', { name: 'description', rows: '2', value: task.description || '' }));
form.appendChild(makeField('Instructions', 'textarea', { name: 'instructions', rows: '4', value: c.instructions || '' }));
form.appendChild(makeField('Model', 'input', { type: 'text', name: 'model', value: c.model || 'sonnet' }));
- form.appendChild(makeField('Working Directory', 'input', { type: 'text', name: 'working_dir', value: c.working_dir || '', placeholder: '/path/to/repo' }));
+ form.appendChild(makeField('Working Directory', 'input', { type: 'text', name: 'project_dir', value: c.project_dir || '', placeholder: '/path/to/repo' }));
form.appendChild(makeField('Max Budget (USD)', 'input', { type: 'number', name: 'max_budget_usd', step: '0.01', value: c.max_budget_usd != null ? String(c.max_budget_usd) : '1.00' }));
form.appendChild(makeField('Timeout', 'input', { type: 'text', name: 'timeout', value: formatDurationForInput(task.timeout) || '15m', placeholder: '15m' }));
@@ -569,7 +571,7 @@ async function handleEditSave(taskId, form, saveBtn) {
claude: {
model: get('model'),
instructions: get('instructions'),
- working_dir: get('working_dir'),
+ project_dir: get('project_dir'),
max_budget_usd: parseFloat(get('max_budget_usd')),
},
timeout: get('timeout'),
@@ -1018,7 +1020,7 @@ async function elaborateTask(prompt, workingDir) {
const res = await fetch(`${API_BASE}/api/tasks/elaborate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ prompt, working_dir: workingDir }),
+ body: JSON.stringify({ prompt, project_dir: workingDir }),
});
if (!res.ok) {
let msg = `HTTP ${res.status}`;
@@ -1048,13 +1050,13 @@ function buildValidatePayload() {
const f = document.getElementById('task-form');
const name = f.querySelector('[name="name"]').value;
const instructions = f.querySelector('[name="instructions"]').value;
- const working_dir = f.querySelector('[name="working_dir"]').value;
+ const project_dir = f.querySelector('[name="project_dir"]').value;
const model = f.querySelector('[name="model"]').value;
const allowedToolsEl = f.querySelector('[name="allowed_tools"]');
const allowed_tools = allowedToolsEl
? allowedToolsEl.value.split(',').map(s => s.trim()).filter(Boolean)
: [];
- return { name, claude: { instructions, working_dir, model, allowed_tools } };
+ return { name, claude: { instructions, project_dir, model, allowed_tools } };
}
function renderValidationResult(result) {
@@ -1167,7 +1169,7 @@ function closeTaskModal() {
}
async function createTask(formData) {
- const selectVal = formData.get('working_dir');
+ const selectVal = formData.get('project_dir');
const workingDir = selectVal === '__new__'
? document.getElementById('new-project-input').value.trim()
: selectVal;
@@ -1177,7 +1179,7 @@ async function createTask(formData) {
claude: {
model: formData.get('model'),
instructions: formData.get('instructions'),
- working_dir: workingDir,
+ project_dir: workingDir,
max_budget_usd: parseFloat(formData.get('max_budget_usd')),
},
timeout: formData.get('timeout'),
@@ -1221,7 +1223,7 @@ async function saveTemplate(formData) {
claude: {
model: formData.get('model'),
instructions: formData.get('instructions'),
- working_dir: formData.get('working_dir'),
+ project_dir: formData.get('project_dir'),
max_budget_usd: parseFloat(formData.get('max_budget_usd')),
allowed_tools: splitTrim(formData.get('allowed_tools') || ''),
},
@@ -1401,7 +1403,7 @@ function renderTaskPanel(task, executions) {
claudeGrid.append(
makeMetaItem('Model', c.model),
makeMetaItem('Max Budget', c.max_budget_usd != null ? `$${c.max_budget_usd.toFixed(2)}` : '—'),
- makeMetaItem('Working Dir', c.working_dir),
+ makeMetaItem('Project Dir', c.project_dir),
makeMetaItem('Permission Mode', c.permission_mode || 'default'),
);
if (c.allowed_tools && c.allowed_tools.length > 0) {
@@ -2071,15 +2073,15 @@ if (typeof document !== 'undefined') document.addEventListener('DOMContentLoaded
f.querySelector('[name="name"]').value = result.name;
if (result.claude && result.claude.instructions)
f.querySelector('[name="instructions"]').value = result.claude.instructions;
- if (result.claude && result.claude.working_dir) {
+ if (result.claude && result.claude.project_dir) {
const sel = document.getElementById('project-select');
- const exists = [...sel.options].some(o => o.value === result.claude.working_dir);
+ const exists = [...sel.options].some(o => o.value === result.claude.project_dir);
if (exists) {
- sel.value = result.claude.working_dir;
+ sel.value = result.claude.project_dir;
} else {
sel.value = '__new__';
document.getElementById('new-project-row').hidden = false;
- document.getElementById('new-project-input').value = result.claude.working_dir;
+ document.getElementById('new-project-input').value = result.claude.project_dir;
}
}
if (result.claude && result.claude.model)
diff --git a/web/index.html b/web/index.html
index 3b7901c..842c272 100644
--- a/web/index.html
+++ b/web/index.html
@@ -24,6 +24,7 @@
<div data-panel="tasks" hidden>
<div class="task-list-toolbar">
<button class="filter-tab active" data-filter="active">Active</button>
+ <button class="filter-tab" data-filter="interrupted">Interrupted</button>
<button class="filter-tab" data-filter="done">Done</button>
<button class="filter-tab" data-filter="all">All</button>
</div>
@@ -62,7 +63,7 @@
</div>
<hr class="form-divider">
<label>Project
- <select name="working_dir" id="project-select">
+ <select name="project_dir" id="project-select">
<option value="/workspace/claudomator" selected>/workspace/claudomator</option>
</select>
</label>
@@ -101,7 +102,7 @@
<label>Description <textarea name="description" rows="2"></textarea></label>
<label>Model <input name="model" value="sonnet"></label>
<label>Instructions <textarea name="instructions" rows="6" required></textarea></label>
- <label>Working Directory <input name="working_dir" placeholder="/path/to/repo"></label>
+ <label>Project Directory <input name="project_dir" placeholder="/path/to/repo"></label>
<label>Max Budget (USD) <input name="max_budget_usd" type="number" step="0.01" value="1.00"></label>
<label>Allowed Tools <input name="allowed_tools" placeholder="Bash, Read, Write"></label>
<label>Timeout <input name="timeout" value="15m"></label>