diff --git a/CHANGELOG.md b/CHANGELOG.md index 5230e15..f0e9f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,14 @@ new version heading in the same commit. ## [Unreleased] -## [0.222.0] — 2026-07-16 +## [0.222.1] — 2026-07-16 +### Fixed +- **Session trail: `task_dispatch` no longer mis-reads as "deleted".** The `task.dispatched` audit event + keys the task id under `data.task` (not `data.id`), so the activity classifier resolved an empty id and + the trail showed the dispatch as `deleted` with a blank summary. It now reads `data.task` (falling back + to `data.id`), so a dispatch shows the task title + its live status; the endpoint also guards + empty-id targets so a missing id yields no status rather than a misleading "deleted". Follow-up to the + v0.221.0 session activity trail. ### Added - **Chat renders KB pages and hosted apps inline too.** Extends the inline-deliverable cards (v0.220.0) beyond Library artifacts: when an agent writes a Knowledge Base page (`kb_write`) or builds/changes a diff --git a/package-lock.json b/package-lock.json index f02653d..9f6998e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agent-os", - "version": "0.222.0", + "version": "0.222.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agent-os", - "version": "0.222.0", + "version": "0.222.1", "license": "MIT", "bin": { "agent-os": "bin/agent-os" diff --git a/package.json b/package.json index 840e988..aafd390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-os", - "version": "0.222.0", + "version": "0.222.1", "description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.", "license": "MIT", "type": "commonjs", diff --git a/scripts/session-trail-test.cjs b/scripts/session-trail-test.cjs index 53b629d..928f6dd 100644 --- a/scripts/session-trail-test.cjs +++ b/scripts/session-trail-test.cjs @@ -40,6 +40,8 @@ const { createHttpServer } = require(path.join(ROOT, 'dist/server.js')); .run(t++, SID, T, type, 'website-bot', JSON.stringify(data)); audit('task.created', { id: 'tk1', title: 'migrate the DB' }); audit('task.updated', { id: 'tk1', status: 'doing' }); + audit('task.dispatched', { task: 'tk1', title: 'migrate the DB', agent: 'website-bot' }); // id lives in data.task + audit('task.dispatched', { title: 'orphan (no id)' }); // no task id → must NOT resolve to "deleted" audit('secret.put', { key: 'STRIPE_KEY', principal: '*' }); audit('secret.requested', { key: 'OPENAI_KEY', mode: 'provide' }); audit('skill.proposed', { name: 'triage-flow', description: 'triage playbook' }); @@ -82,6 +84,10 @@ const { createHttpServer } = require(path.join(ROOT, 'dist/server.js')); // (2) live status resolution assert(by('task_create').status === 'doing' && by('task_create').statusTone === 'open', 'task → live status "doing" (open)', JSON.stringify(by('task_create'))); assert(by('task_update').status === 'doing', 'task_update entry also resolves to current "doing"'); + // task.dispatched keys the id under data.task; an id-bearing dispatch resolves, an orphan stays blank + const disp = ev.filter((e) => e.primitive === 'task_dispatch'); + assert(disp.some((e) => e.target && e.target.id === 'tk1' && e.status === 'doing'), 'task_dispatch (data.task=tk1) → resolves to "doing"', JSON.stringify(disp)); + assert(disp.some((e) => !e.status && !(e.target && e.target.id)), 'task_dispatch with no id → no target, no misleading "deleted" status'); assert(by('secret_put').status === 'stored', 'secret → "stored" (exists in vault)', JSON.stringify(by('secret_put'))); assert(by('kb_write').status === 'rev 3' && by('kb_write').statusTone === 'muted', 'kb_write → current "rev 3"', JSON.stringify(by('kb_write'))); assert(by('secret_request').status === 'approved' && by('secret_request').statusTone === 'done', 'secret_request → card status "approved" (done)', JSON.stringify(by('secret_request'))); diff --git a/src/server.ts b/src/server.ts index 18542f9..4e55ec3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2392,7 +2392,7 @@ async function handle(os: AgentOS, tm: TerminalManager, autos: Automations, req: return out; }; for (const e of events) { - if (!e.target) continue; + if (!e.target || !e.target.id) continue; // no id to locate the object → nothing to resolve (not "deleted") const r = resolveStatus(e.target); if (r) { e.status = r.status; e.statusTone = r.tone; } } diff --git a/src/state/session-activity.ts b/src/state/session-activity.ts index 7758a92..ee4f14f 100644 --- a/src/state/session-activity.ts +++ b/src/state/session-activity.ts @@ -133,7 +133,7 @@ export function classifyActivity(type: string, data: Record): A case 'task.claimed': return { category: 'tasks', primitive: 'task_claim', summary: id(), target: { kind: 'task', id: id() } }; case 'task.updated': return { category: 'tasks', primitive: 'task_update', summary: `${id()} → ${str(data.status)}`, target: { kind: 'task', id: id() } }; case 'task.completed': return { category: 'tasks', primitive: 'task_update', summary: `${id()} → ${str(data.status) || 'done'}`, effect: 'allow', target: { kind: 'task', id: id() } }; - case 'task.dispatched': return { category: 'tasks', primitive: 'task_dispatch', summary: id(), target: { kind: 'task', id: id() } }; + case 'task.dispatched': { const tid = str(data.task) || id(); return { category: 'tasks', primitive: 'task_dispatch', summary: clipText(data.title) || tid, target: tid ? { kind: 'task', id: tid } : undefined }; } case 'task.deleted': return { category: 'tasks', primitive: 'task_delete', summary: id(), target: { kind: 'task', id: id() } }; // ── secrets vault (shared credential handoff) ──