Import ducklm runtime

This commit is contained in:
2026-05-10 23:37:56 +08:00
parent fd1a045488
commit dc8267880a
90 changed files with 9171 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
"""Event bus and event store."""
+32
View File
@@ -0,0 +1,32 @@
from __future__ import annotations
from typing import Callable
from app.core.contracts import RuntimeEvent
from app.events.event_store import SQLiteEventStore
Subscriber = Callable[[RuntimeEvent], None]
class EventBus:
"""Per-task ordered event publishing with durable storage."""
def __init__(self, event_store: SQLiteEventStore) -> None:
self._store = event_store
self._subscribers: list[Subscriber] = []
def next_sequence(self, task_id: str) -> int:
return self._store.get_latest_sequence(task_id) + 1
def publish(self, event: RuntimeEvent) -> RuntimeEvent:
self._store.append(event)
for subscriber in self._subscribers:
subscriber(event)
return event
def subscribe(self, subscriber: Subscriber) -> None:
self._subscribers.append(subscriber)
def list_for_task(self, task_id: str) -> list[RuntimeEvent]:
return self._store.list_for_task(task_id)
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
import json
import sqlite3
from pathlib import Path
from app.core.contracts import RuntimeEvent
class SQLiteEventStore:
"""Append-only event store with per-task ordered history."""
def __init__(self, db_path: str | Path) -> None:
self._db_path = Path(db_path)
self._db_path.parent.mkdir(parents=True, exist_ok=True)
self._initialize()
def append(self, event: RuntimeEvent) -> None:
with sqlite3.connect(self._db_path) as conn:
conn.execute(
"""
INSERT INTO events (
event_id, task_id, session_id, sequence, type, timestamp,
payload_json, causation_id, correlation_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
event.event_id,
event.task_id,
event.session_id,
event.sequence,
event.type,
event.timestamp.isoformat(),
json.dumps(event.payload),
event.causation_id,
event.correlation_id,
),
)
conn.commit()
def list_for_task(self, task_id: str) -> list[RuntimeEvent]:
with sqlite3.connect(self._db_path) as conn:
rows = conn.execute(
"""
SELECT event_id, task_id, session_id, sequence, type, timestamp,
payload_json, causation_id, correlation_id
FROM events
WHERE task_id = ?
ORDER BY sequence ASC
""",
(task_id,),
).fetchall()
return [
RuntimeEvent(
event_id=row[0],
task_id=row[1],
session_id=row[2],
sequence=row[3],
type=row[4],
timestamp=row[5],
payload=json.loads(row[6]),
causation_id=row[7],
correlation_id=row[8],
)
for row in rows
]
def get_latest_sequence(self, task_id: str) -> int:
with sqlite3.connect(self._db_path) as conn:
row = conn.execute(
"SELECT COALESCE(MAX(sequence), 0) FROM events WHERE task_id = ?",
(task_id,),
).fetchone()
return int(row[0]) if row else 0
def _initialize(self) -> None:
with sqlite3.connect(self._db_path) as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS events (
event_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
session_id TEXT NOT NULL,
sequence INTEGER NOT NULL,
type TEXT NOT NULL,
timestamp TEXT NOT NULL,
payload_json TEXT NOT NULL,
causation_id TEXT,
correlation_id TEXT NOT NULL,
UNIQUE(task_id, sequence)
)
"""
)
conn.commit()
+31
View File
@@ -0,0 +1,31 @@
TASK_RECEIVED = "task_received"
CONTEXT_BUILT = "context_built"
STEP_STARTED = "step_started"
TOOL_CALLED = "tool_called"
TOOL_COMPLETED = "tool_completed"
PERMISSION_REQUESTED = "permission_requested"
PERMISSION_RESOLVED = "permission_resolved"
TASK_AWAITING_PERMISSION = "task_awaiting_permission"
SECRET_REQUESTED = "secret_requested"
TASK_AWAITING_INPUT = "task_awaiting_input"
CHECKPOINT_SAVED = "checkpoint_saved"
TASK_COMPLETED = "task_completed"
TASK_FAILED = "task_failed"
ORCHESTRATOR_CALLED = "orchestrator_called"
ORCHESTRATOR_RESULT = "orchestrator_result"
ORCHESTRATOR_UNAVAILABLE = "orchestrator_unavailable"
ORCHESTRATOR_FALLBACK_USED = "orchestrator_fallback_used"
ORCHESTRATOR_RETRY = "orchestrator_retry"
PLANNER_CALLED = "planner_called"
PLANNER_RETRY = "planner_retry"
CRITIC_CALLED = "critic_called"
CRITIC_RESULT = "critic_result"
MEMORY_WRITE_DECIDED = "memory_write_decided"
PLAN_STARTED = "plan_started"
PLAN_FAILED = "plan_failed"
PLAN_COMPLETED = "plan_completed"
STEPPED_COMPLETED = "step_completed"
THINKER_CALLED = "thinker_called"
THINKER_RESULT = "thinker_result"
JSON_COMPILER_CALLED = "json_compiler_called"
JSON_COMPILER_RESULT = "json_compiler_result"