From 8b034f0476b104a026e13129c2d0e041ae3bab55 Mon Sep 17 00:00:00 2001 From: Igor Date: Sun, 10 May 2026 06:45:59 +0700 Subject: [PATCH 01/14] Add project automation management --- .../raw/features/project-cron-automations.md | 13 ++ .../wiki/concepts/project-cron-automations.md | 23 +++ llm-wiki/wiki/index.md | 2 + llm-wiki/wiki/log.md | 3 + src/api/codexGateway.ts | 51 +++++ src/components/sidebar/SidebarThreadTree.vue | 146 ++++++++++++-- src/server/codexAppServerBridge.ts | 190 +++++++++++++++++- src/types/codex.ts | 1 + tests.md | 106 +++++----- 9 files changed, 464 insertions(+), 71 deletions(-) create mode 100644 llm-wiki/raw/features/project-cron-automations.md create mode 100644 llm-wiki/wiki/concepts/project-cron-automations.md diff --git a/llm-wiki/raw/features/project-cron-automations.md b/llm-wiki/raw/features/project-cron-automations.md new file mode 100644 index 000000000..66689c13d --- /dev/null +++ b/llm-wiki/raw/features/project-cron-automations.md @@ -0,0 +1,13 @@ +# Project cron automations source + +Date: 2026-05-10 + +Project-scoped automations are represented as Codex cron automations with a `cwds` array containing the project folder path. Thread automations remain heartbeat automations keyed by `target_thread_id`. + +Implementation facts: +- `src/server/codexAppServerBridge.ts` parses and serializes `cwds` in automation TOML records. +- `GET /codex-api/project-automations` returns project cron automations grouped by project cwd. +- `GET`, `PUT`, and `DELETE /codex-api/project-automation` read, save, and remove automations for one project cwd. +- `src/api/codexGateway.ts` exposes project automation helpers mirroring the thread automation helpers. +- `src/components/sidebar/SidebarThreadTree.vue` adds project menu `Add automation…` / `Manage automations…`, project row `Auto` chips, and reuses the existing automation dialog with project-specific copy. +- Project automations intentionally do not expose `Run now`; the existing manual run behavior remains thread-heartbeat-only. diff --git a/llm-wiki/wiki/concepts/project-cron-automations.md b/llm-wiki/wiki/concepts/project-cron-automations.md new file mode 100644 index 000000000..e24182a0a --- /dev/null +++ b/llm-wiki/wiki/concepts/project-cron-automations.md @@ -0,0 +1,23 @@ +# Project Cron Automations + +Project automations extend the existing sidebar automation UI from thread-scoped heartbeat records to project-scoped cron records. + +## Storage + +Project automations use Codex cron automation TOML records with `cwds = [""]`. This matches Codex's project/folder automation shape while preserving thread heartbeat records that use `target_thread_id`. + +Source: [project-cron-automations.md](../../raw/features/project-cron-automations.md) + +## UI + +The sidebar project row dots menu exposes `Add automation…` or `Manage automations…`. The dialog reuses the thread automation manager style, including multiple automation selection, schedule presets, status, and remove/save behavior. + +Project rows show the same compact `Auto` chip when at least one project automation is attached. + +Source: [project-cron-automations.md](../../raw/features/project-cron-automations.md) + +## Boundaries + +`Run now` remains available only for thread heartbeat automations because it queues a heartbeat message into a concrete thread. Project cron automations are scheduled against one or more working directories instead. + +Source: [project-cron-automations.md](../../raw/features/project-cron-automations.md) diff --git a/llm-wiki/wiki/index.md b/llm-wiki/wiki/index.md index 94b762bd3..21349b557 100644 --- a/llm-wiki/wiki/index.md +++ b/llm-wiki/wiki/index.md @@ -14,6 +14,7 @@ - [concepts/realtime-chat-rendering.md](./concepts/realtime-chat-rendering.md): realtime chat rendering, sync-churn reduction, and inline media sanitization. - [concepts/skills-route-ui.md](./concepts/skills-route-ui.md): Skills route naming, first-launch Plugins card persistence, dark-theme fixes, and verification lessons. - [concepts/thread-heartbeat-automations.md](./concepts/thread-heartbeat-automations.md): thread-scoped heartbeat automation storage, multi-automation management, and manual run behavior. +- [concepts/project-cron-automations.md](./concepts/project-cron-automations.md): project-scoped cron automation storage and sidebar management UI. ## Sources - [../raw/features/integrated-terminal.md](../raw/features/integrated-terminal.md): source facts for the integrated terminal implementation and follow-up tests. @@ -21,6 +22,7 @@ - [../raw/features/realtime-chat-rendering-inline-media.md](../raw/features/realtime-chat-rendering-inline-media.md): source facts for realtime chat rendering and inline media sanitization. - [../raw/features/skills-route-ui-and-first-launch-card.md](../raw/features/skills-route-ui-and-first-launch-card.md): source facts for the Skills route rename, first-launch Plugins card, dark-theme fix, and dev-server workflow adjustment. - [../raw/features/thread-heartbeat-automations.md](../raw/features/thread-heartbeat-automations.md): source facts for thread heartbeat automations, multiple automations per thread, and Run now queue behavior. +- [../raw/features/project-cron-automations.md](../raw/features/project-cron-automations.md): source facts for project cron automations in the sidebar. - [../raw/projects/codex-web-local.md](../raw/projects/codex-web-local.md): immutable source snapshot for project facts. - [../raw/fixes/opencode-zen-big-pickle-codex-cli.md](../raw/fixes/opencode-zen-big-pickle-codex-cli.md): Big Pickle + Codex CLI fix details. - [../raw/fixes/opencode-zen-reasoning-content-proxy.md](../raw/fixes/opencode-zen-reasoning-content-proxy.md): Codex Web Local Zen proxy reasoning_content round-trip fix and Docker verification. diff --git a/llm-wiki/wiki/log.md b/llm-wiki/wiki/log.md index 6f1bc3d43..11da0e02e 100644 --- a/llm-wiki/wiki/log.md +++ b/llm-wiki/wiki/log.md @@ -46,3 +46,6 @@ - Updated wiki page: `concepts/opencode-zen-big-pickle.md`. - Documents: DeepSeek thinking-mode `reasoning_content` round-trip requirement, Chat-shaped Zen proxy endpoint selection, streaming reasoning preservation, Docker validation, and the `/tmp/app.tar` restart gotcha. - Updated `index.md`. +## 2026-05-10 + +- Added project cron automation notes for sidebar project-level automation management. diff --git a/src/api/codexGateway.ts b/src/api/codexGateway.ts index 11146893f..b5eb53a42 100644 --- a/src/api/codexGateway.ts +++ b/src/api/codexGateway.ts @@ -1039,6 +1039,7 @@ function asAutomation(record: unknown): UiThreadAutomation | null { rrule, status, targetThreadId: readString(row.targetThreadId), + cwds: Array.isArray(row.cwds) ? row.cwds.filter((item): item is string => typeof item === 'string' && item.trim().length > 0) : [], createdAtMs: readNumber(row.createdAtMs), updatedAtMs: readNumber(row.updatedAtMs), nextRunAtMs: readNumber(row.nextRunAtMs), @@ -1070,6 +1071,22 @@ export async function getThreadAutomationMap(): Promise> { + const response = await fetch('/codex-api/project-automations') + const payload = await response.json().catch(() => null) + if (!response.ok) { + throw new Error(extractErrorMessage(payload, 'Failed to load project automations')) + } + const data = asRecord(asRecord(payload)?.data) + const next: Record = {} + if (!data) return next + for (const [projectName, value] of Object.entries(data)) { + const automations = asAutomationArray(value) + if (automations.length > 0) next[projectName] = automations + } + return next +} + export async function getThreadAutomation(threadId: string, automationId?: string): Promise { const query = new URLSearchParams({ threadId }) if (automationId) query.set('automationId', automationId) @@ -1105,6 +1122,28 @@ export async function upsertThreadAutomation(input: { return automation } +export async function upsertProjectAutomation(input: { + projectName: string + id?: string + name: string + prompt: string + rrule: string + status: UiThreadAutomationStatus +}): Promise { + const response = await fetch('/codex-api/project-automation', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(input), + }) + const payload = await response.json().catch(() => null) + if (!response.ok) { + throw new Error(extractErrorMessage(payload, 'Failed to save project automation')) + } + const automation = asAutomation(asRecord(payload)?.data) + if (!automation) throw new Error('Project automation response was malformed') + return automation +} + export async function deleteThreadAutomation(threadId: string, automationId?: string): Promise { const query = new URLSearchParams({ threadId }) if (automationId) query.set('automationId', automationId) @@ -1117,6 +1156,18 @@ export async function deleteThreadAutomation(threadId: string, automationId?: st } } +export async function deleteProjectAutomation(projectName: string, automationId?: string): Promise { + const query = new URLSearchParams({ projectName }) + if (automationId) query.set('automationId', automationId) + const response = await fetch(`/codex-api/project-automation?${query.toString()}`, { + method: 'DELETE', + }) + const payload = await response.json().catch(() => null) + if (!response.ok) { + throw new Error(extractErrorMessage(payload, 'Failed to delete project automation')) + } +} + export async function runThreadAutomationNow(threadId: string, automationId: string): Promise { const response = await fetch('/codex-api/thread-automation/run', { method: 'POST', diff --git a/src/components/sidebar/SidebarThreadTree.vue b/src/components/sidebar/SidebarThreadTree.vue index 6609a7040..4a00f42e9 100644 --- a/src/components/sidebar/SidebarThreadTree.vue +++ b/src/components/sidebar/SidebarThreadTree.vue @@ -293,6 +293,13 @@ {{ getProjectVisibleName(group) }} + + Auto +