From 595dd082a7ebeffd02e7c6a83bdead621fc57fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 13 Aug 2026 01:56:07 +0000 Subject: [PATCH] delete Task Logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A complete read path over a table nothing could write to. task-logger.ts exported createTaskLog, appendToLog and finalizeLog, and none of the three was called anywhere in the tree — so `task_logs` could never gain a row, and the screen was permanently empty for everyone. The read half was fully wired: mounted router, capability claim, dock icon, two routes and a page-title rule. That is why it looked alive. Gone, in the order it was reached: Screens/Dashboard/TaskLogs/ the screen App.tsx /task-logs and /task-logs/:id Dashboard/index.tsx the export Layout/Dock.tsx the 'Logs' icon, and ScrollText with it state/usePageTitle.ts the title rule api/task-logs/task-logs.ts the router, and its mount in hono.ts api/task-logger.ts 101 lines of orphaned writer officer_db/src/operations/ the directory officer_db/src/schema.ts the export line officer_db/src/types.ts TaskLogSelect / TaskLogInsert capabilities/registry.ts loses '/task-logs' from the `tasks` capability's `api` AND `routes`. The api half is not optional: assertCapabilityTotality check 2 refuses to boot on a capability claiming a prefix nothing mounts, so unmounting the router while leaving the claim would have stopped the server starting. db:push now creates 21 tables, down from 43 at the start of the evening. officer_db/src/operations was one of the two lopsided directories the schema/queries merge exposed. integrations/ is the remaining one, and it is legitimate — it spans server and user-data. Co-Authored-By: Claude Opus 5 (1M context) --- src/apps/officer-web/App.tsx | 2 - .../Screens/Dashboard/Layout/Dock.tsx | 2 - .../Screens/Dashboard/TaskLogs/index.tsx | 175 ------------------ .../officer-web/Screens/Dashboard/index.tsx | 1 - src/apps/officer-web/state/usePageTitle.ts | 1 - src/databases/CLAUDE.md | 6 +- src/databases/officer_db/src/index.ts | 3 - .../officer_db/src/operations/schema.ts | 35 ---- src/databases/officer_db/src/schema.ts | 1 - src/databases/officer_db/src/types.ts | 4 - src/servers/api/task-logger.ts | 101 ---------- src/servers/api/task-logs/task-logs.ts | 47 ----- src/servers/capabilities/registry.ts | 4 +- src/servers/hono.ts | 2 - 14 files changed, 5 insertions(+), 379 deletions(-) delete mode 100644 src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx delete mode 100644 src/databases/officer_db/src/operations/schema.ts delete mode 100644 src/servers/api/task-logger.ts delete mode 100644 src/servers/api/task-logs/task-logs.ts diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 4bd300b2..adb6038f 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -91,8 +91,6 @@ export function App() { } /> } /> } /> - } /> - } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx index 14598762..7626c0fd 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx @@ -130,7 +130,6 @@ import { FolderOpen, Code, LayoutGrid, - ScrollText, FolderKanban, Monitor, Mail, @@ -170,7 +169,6 @@ export const CORE_DOCK_ITEMS: DockItem[] = [ { label: 'Gitea', to: '/gitea', icon: GitBranch, color: '#609926' }, { label: 'Editor', to: '/code-editor', icon: Code, color: '#a78bfa' }, { label: 'Jobs', to: '/jobs', icon: Workflow, color: '#14b8a6' }, - { label: 'Logs', to: '/task-logs', icon: ScrollText, color: '#94a3b8' }, { label: 'Terminal', to: '/terminal', icon: Monitor, color: '#f97316' }, { label: 'Browser', to: '/browser', icon: Globe, color: '#06b6d4' }, { label: 'Monitor', to: '/system-monitor', icon: Activity, color: '#0ea5e9' }, diff --git a/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx b/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx deleted file mode 100644 index 30b5c101..00000000 --- a/src/apps/officer-web/Screens/Dashboard/TaskLogs/index.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Link, useParams } from 'react-router'; -import { Search, AlertCircle, CheckCircle2, Clock, ArrowLeft } from 'lucide-react'; -import { useClient } from 'hooks/useClient'; -import { Card } from '@/components/Card'; -import { MessageBubble, type ChatMessage } from 'officerdev'; - -type LogMetadata = { - id: number; - taskName: string; - taskDirName: string; - entryName: string; - entryType: string; - provider: string; - model: string; - isError: boolean; - startedAt: string; - completedAt: string | null; -}; - -type FullLog = LogMetadata & { - messages: ChatMessage[]; -}; - -const formatDate = (iso: string) => { - const d = new Date(iso); - return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); -}; - -const ProviderBadge = ({ provider }: { provider: string }) => ( - - {provider} - -); - -// Which run is open is `/task-logs/:id`. No redirect guard — the bare route is the list with nothing -// open, and an id that no longer exists gets the empty pane rather than a rewritten address. -export const TaskLogs = () => { - const client = useClient(); - const [logs, setLogs] = useState([]); - const selectedId = useParams<{ id: string }>().id ?? null; - const [selectedLog, setSelectedLog] = useState(null); - const [search, setSearch] = useState(''); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - client - .get('/task-logs') - .then((data) => { - setLogs(data); - setIsLoading(false); - }) - .catch(() => setIsLoading(false)); - }, []); - - useEffect(() => { - if (!selectedId) { - setSelectedLog(null); - return; - } - client - .get(`/task-logs/${selectedId}`) - .then(setSelectedLog) - .catch(() => setSelectedLog(null)); - }, [selectedId]); - - const filtered = search - ? logs.filter((l) => { - const q = search.toLowerCase(); - return ( - l.taskName.toLowerCase().includes(q) || - l.entryName.toLowerCase().includes(q) || - l.provider.toLowerCase().includes(q) - ); - }) - : logs; - - return ( -
- {/* Left panel: list */} - -
-
- - setSearch(ev.target.value)} - className="w-full pl-8 pr-3 py-1.5 text-sm rounded-md border border-duck-dark/15 bg-background/60 text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/40" - /> -
-
- -
- {isLoading && ( -
Loading...
- )} - {!isLoading && filtered.length === 0 && ( -
No logs found
- )} - {filtered.map((log) => ( - -
- {log.isError ? ( - - ) : log.completedAt ? ( - - ) : ( - - )} - {log.taskName} -
-
- {log.entryName} - -
-
{formatDate(log.startedAt)}
- - ))} -
-
- - {/* Right panel: log viewer */} - - {!selectedLog && ( -
- Select a log to view - - - Back to list - -
- )} - {selectedLog && ( - <> -
- - - -
-
- {selectedLog.taskName} - -
-
- {selectedLog.entryName} · {selectedLog.model} · {formatDate(selectedLog.startedAt)} - {selectedLog.completedAt && ` — ${formatDate(selectedLog.completedAt)}`} -
-
- {selectedLog.isError && ( - - Error - - )} -
-
- {selectedLog.messages.map((msg, i) => ( - - ))} -
- - )} -
-
- ); -}; diff --git a/src/apps/officer-web/Screens/Dashboard/index.tsx b/src/apps/officer-web/Screens/Dashboard/index.tsx index 301345de..edc23121 100644 --- a/src/apps/officer-web/Screens/Dashboard/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/index.tsx @@ -6,7 +6,6 @@ export * from './Processes'; export * from './CapabilityPage'; export * from './Settings'; export * from './Skills'; -export * from './TaskLogs'; export * from './Tasks'; export * from './Files'; diff --git a/src/apps/officer-web/state/usePageTitle.ts b/src/apps/officer-web/state/usePageTitle.ts index 5dfa6876..4faac0f3 100644 --- a/src/apps/officer-web/state/usePageTitle.ts +++ b/src/apps/officer-web/state/usePageTitle.ts @@ -34,7 +34,6 @@ const RULES: TitleRule[] = [ { match: (p) => p.startsWith('/qr-transfer'), title: 'QR Transfer' }, { match: (p) => p.startsWith('/activity'), title: 'Activity' }, { match: (p) => p.startsWith('/code-editor'), title: 'Code Editor' }, - { match: (p) => p.startsWith('/task-logs'), title: 'Task Logs' }, { match: (p) => p.startsWith('/tasks'), title: 'Tasks' }, { match: (p) => p.startsWith('/jobs'), title: 'Jobs' }, { match: (p) => p.startsWith('/skills'), title: 'Skills' }, diff --git a/src/databases/CLAUDE.md b/src/databases/CLAUDE.md index 33b271e6..5f0e93bf 100644 --- a/src/databases/CLAUDE.md +++ b/src/databases/CLAUDE.md @@ -33,9 +33,9 @@ features listed twice, `db` and `schema` buried at line 270 with three feature b (`app-store`/`sidecar-installs`, `email`/`email-accounts`, `server`/`server-config`), `operations` had no query file at all, and `integrations` had no schema file. -Two directories are still lopsided and say so by their contents: `operations/` has only a schema (its -`task_logs` is reached directly from `src/servers/`, bypassing this package), and `integrations/` has only -queries, because it spans `server` and `user-data`. +One directory is still lopsided and says so by its contents: `integrations/` has only queries, because it +spans `server` and `user-data`. (`operations/` was the other, and was deleted on 2026-08-13 along with the +Task Logs feature — see below.) **`src/schema.ts` is drizzle-kit's view, not the runtime's.** `drizzle.config.ts` points at it, so a commented line there removes a table from the DATABASE without removing a line of code — every query diff --git a/src/databases/officer_db/src/index.ts b/src/databases/officer_db/src/index.ts index 261420db..1de46eef 100644 --- a/src/databases/officer_db/src/index.ts +++ b/src/databases/officer_db/src/index.ts @@ -40,6 +40,3 @@ export * from './user-data'; export * from './vault'; export * from './wallet'; -// `operations` is deliberately absent: it has a schema and no queries. Its one table, `task_logs`, is -// reached as `schema.taskLogs` from src/servers/api/task-logger.ts, which reaches past this package's -// own boundary. Give it a queries.ts and it earns a line here. diff --git a/src/databases/officer_db/src/operations/schema.ts b/src/databases/officer_db/src/operations/schema.ts deleted file mode 100644 index d60abe5f..00000000 --- a/src/databases/officer_db/src/operations/schema.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Task run logs. -// -// This file held two other tables until 2026-08-13, both dead and both removed: -// -// queue_jobs the background job engine's state, from when it kept it in Postgres. It works -// on files now (src/servers/queue/storage.ts) and had not read this table since. -// terminal_containers a docker id and port per user, from the architecture where every account ran -// inside its own container. That is gone — see data-path.ts on getHomeDir. -// -// Neither had a single reader or writer anywhere in the tree. They were created on every fresh install -// by db:push and then never touched. -// -// What is left is one table with no queries.ts beside it: task_logs is reached as `schema.taskLogs` -// from src/servers/api/task-logger.ts, which reaches past this package's boundary. That is the next -// thing to fix here. - -import { pgTable, serial, text, integer, boolean, timestamp, jsonb, index } from 'drizzle-orm/pg-core'; -import { users } from '../auth/schema'; - -export const taskLogs = pgTable('task_logs', { - id: serial('id').primaryKey(), - userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), - taskName: text('task_name').notNull(), - taskDirName: text('task_dir_name').notNull(), - entryName: text('entry_name').notNull(), - entryType: text('entry_type').notNull(), - provider: text('provider').notNull(), - model: text('model').notNull(), - isError: boolean('is_error').notNull().default(false), - messages: jsonb('messages').notNull().default([]), - startedAt: timestamp('started_at', { withTimezone: true }).notNull(), - completedAt: timestamp('completed_at', { withTimezone: true }), -}, (table) => [ - index('idx_task_logs_user_started').on(table.userId, table.startedAt), -]); diff --git a/src/databases/officer_db/src/schema.ts b/src/databases/officer_db/src/schema.ts index b4eb4108..25022f5c 100644 --- a/src/databases/officer_db/src/schema.ts +++ b/src/databases/officer_db/src/schema.ts @@ -27,7 +27,6 @@ export * from './api-keys/schema'; // api_keys export * from './user-data/schema'; // user_settings, user_state, user_integrations, dock_configs export * from './dashboards/schema'; // dashboards, screens, dashboard_defaults export * from './server/schema'; // server_config (SMTP lives here), server_integrations -export * from './operations/schema'; // task_logs export * from './pipeline-jobs/schema'; // pipeline_jobs export * from './chat-events/schema'; // chat_session_events export * from './agent-panels/schema'; // agent_panels diff --git a/src/databases/officer_db/src/types.ts b/src/databases/officer_db/src/types.ts index 454f0ca6..7ad8cd51 100644 --- a/src/databases/officer_db/src/types.ts +++ b/src/databases/officer_db/src/types.ts @@ -44,10 +44,6 @@ export type DashboardInsert = typeof Schema.dashboards.$inferInsert; export type ScreenSelect = typeof Schema.screens.$inferSelect; export type ScreenInsert = typeof Schema.screens.$inferInsert; -// ── Operations ── - -export type TaskLogSelect = typeof Schema.taskLogs.$inferSelect; -export type TaskLogInsert = typeof Schema.taskLogs.$inferInsert; // ── Email ── diff --git a/src/servers/api/task-logger.ts b/src/servers/api/task-logger.ts deleted file mode 100644 index 59164a4d..00000000 --- a/src/servers/api/task-logger.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { db, schema } from 'officerdb'; -import type { TaskInfo } from '@@/api/chat-types'; - -type ChatMessage = - | { role: 'user'; text: string } - | { role: 'assistant'; text: string } - | { - role: 'tool'; - toolName: string; - toolInput: Record; - toolUseId: string; - output?: string; - isError?: boolean; - } - | { role: 'result'; costUsd: number; durationMs: number; numTurns: number; isError: boolean } - | { role: 'error'; text: string }; - -type TaskLog = { - userId: number; - taskName: string; - taskDirName: string; - entryName: string; - entryType: 'file' | 'directory'; - provider: string; - model: string; - startedAt: Date; - messages: ChatMessage[]; -}; - -type LogEntry = { - userId: number; - log: TaskLog; -}; - -const activeLogs = new Map(); -let logCounter = 0; - -export function createTaskLog(userId: number, taskInfo: TaskInfo, provider: string, model: string): string { - const logId = `log_${Date.now()}_${++logCounter}`; - - const log: TaskLog = { - userId, - taskName: taskInfo.taskName, - taskDirName: taskInfo.taskDirName, - entryName: taskInfo.entryName, - entryType: taskInfo.entryType, - provider, - model, - startedAt: new Date(), - messages: [], - }; - - activeLogs.set(logId, { userId, log }); - return logId; -} - -export function appendToLog(logId: string, message: ChatMessage) { - const entry = activeLogs.get(logId); - if (!entry) return; - - // For tool results, update the existing tool message instead of appending - if (message.role === 'tool' && message.output !== undefined) { - const existing = entry.log.messages.find((m) => m.role === 'tool' && m.toolUseId === message.toolUseId); - if (existing && existing.role === 'tool') { - existing.output = message.output; - existing.isError = message.isError; - return; - } - } - - entry.log.messages.push(message); -} - -export async function finalizeLog(logId: string) { - const entry = activeLogs.get(logId); - if (!entry) return; - - const { log } = entry; - const lastMessage = log.messages[log.messages.length - 1]; - const isError = lastMessage?.role === 'result' ? lastMessage.isError : lastMessage?.role === 'error'; - - try { - await db.insert(schema.taskLogs).values({ - userId: log.userId, - taskName: log.taskName, - taskDirName: log.taskDirName, - entryName: log.entryName, - entryType: log.entryType, - provider: log.provider, - model: log.model, - isError: !!isError, - messages: log.messages, - startedAt: log.startedAt, - completedAt: new Date(), - }); - } catch (err) { - console.error('[task-logger] Failed to write log:', err); - } - - activeLogs.delete(logId); -} diff --git a/src/servers/api/task-logs/task-logs.ts b/src/servers/api/task-logs/task-logs.ts deleted file mode 100644 index 12dfc35e..00000000 --- a/src/servers/api/task-logs/task-logs.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { desc, eq } from 'drizzle-orm'; -import { db, schema } from 'officerdb'; -import { createRouter } from '../../create-router'; - -export const taskLogsRouter = createRouter(); - -// GET / — list all logs (metadata only, no messages) -taskLogsRouter.get('/', async (ctx) => { - const userId = ctx.get('user').id; - - const logs = await db - .select({ - id: schema.taskLogs.id, - taskName: schema.taskLogs.taskName, - taskDirName: schema.taskLogs.taskDirName, - entryName: schema.taskLogs.entryName, - entryType: schema.taskLogs.entryType, - provider: schema.taskLogs.provider, - model: schema.taskLogs.model, - isError: schema.taskLogs.isError, - startedAt: schema.taskLogs.startedAt, - completedAt: schema.taskLogs.completedAt, - }) - .from(schema.taskLogs) - .where(eq(schema.taskLogs.userId, userId)) - .orderBy(desc(schema.taskLogs.startedAt)); - - return ctx.json(logs); -}); - -// GET /:id — return full log with messages -taskLogsRouter.get('/:id', async (ctx) => { - const userId = ctx.get('user').id; - const id = Number(ctx.req.param('id')); - - if (Number.isNaN(id)) { - return ctx.text('Invalid id', 400); - } - - const [log] = await db.select().from(schema.taskLogs).where(eq(schema.taskLogs.id, id)); - - if (!log || log.userId !== userId) { - return ctx.text('Not found', 404); - } - - return ctx.json(log); -}); diff --git a/src/servers/capabilities/registry.ts b/src/servers/capabilities/registry.ts index 5f9c19bd..8852f14f 100644 --- a/src/servers/capabilities/registry.ts +++ b/src/servers/capabilities/registry.ts @@ -307,9 +307,9 @@ export const CAPABILITIES: Capability[] = [ label: 'Tasks and jobs', description: 'Running capabilities, pipelines and background jobs', kind: 'execution', - api: ['/tasks', '/jobs', '/pipeline-jobs', '/task-logs', '/queue'], + api: ['/tasks', '/jobs', '/pipeline-jobs', '/queue'], ws: ['task-runner', 'pipeline'], - routes: ['/jobs', '/task-logs'], + routes: ['/jobs'], }, { key: 'items', diff --git a/src/servers/hono.ts b/src/servers/hono.ts index 8521c143..96b00817 100644 --- a/src/servers/hono.ts +++ b/src/servers/hono.ts @@ -18,7 +18,6 @@ import { scrapeRouter } from './api/scrape/scrape'; import { uploadRouter } from './api/upload/upload'; import { settingsRouter } from './api/settings/settings'; import { dashboardsRouter } from './api/dashboards'; -import { taskLogsRouter } from './api/task-logs/task-logs'; import { router as fileBrowserRouter } from './api/file-browser/router'; import { musicRouter } from './api/music/router'; import { vaultRouter } from './api/vault/router'; @@ -199,7 +198,6 @@ const PROTECTED_MOUNTS: [prefix: string, router: ReturnType ['/user', settingsRouter], ['/api-keys', apiKeysRouter], // your own keys; the `account` core capability covers it ['/dashboards', dashboardsRouter], - ['/task-logs', taskLogsRouter], ['/file-browser', fileBrowserRouter], ['/music', musicRouter], ['/slskd', slskdRouter],