← Files Portable ResumeARCHIVED FILE

skills/.portable-resume/runtime/portable_resume/adapters/goose.py

21.1 KB · Oct 5, 2026 · 18:33 UTC

↓ Download file

"""Read goose sessions.db SQLite as inert context.

Pinned format: ``goose-sessions-sqlite-v15`` (``schema_version`` max = 15).
Default store: ``<root>/sessions/sessions.db`` under the goose data directory
(``$XDG_DATA_HOME/goose`` or ``~/.local/share/goose``).

Never invokes goose CLI/Desktop, Chat Recall, MCP, ACP, or migrations.
"""

from __future__ import annotations

import json
import os
import re
import sqlite3
import stat
from dataclasses import replace
from datetime import datetime, timezone
from typing import Any, Mapping

from ..bounds import DEFAULT_BOUNDS, ReadBudget
from ..diagnostics import DiagnosticError
from ..model import Query, Session, SessionSummary, Turn
from ..paths import canonical_root, canonicalize_cwd, is_within, same_cwd
from ..sanitize import sanitize_turn_record
from ..snapshot import private_sqlite_connection, query_only_live_sqlite
from .base import CapabilityReport, ResolvedRef
from .common import within_age

FORMAT_ID = "goose-sessions-sqlite-v15"
SCHEMA_VERSION = 15
DB_REL = os.path.join("sessions", "sessions.db")
_DEFAULT_EXCLUDE_TYPES = frozenset(
    {"scheduled", "sub_agent", "hidden", "gateway", "acp", "terminal"}
)
_REQUIRED_TABLES = frozenset({"schema_version", "sessions", "messages", "usage_ledger"})
_SESSION_COLUMNS = frozenset(
    {
        "id",
        "name",
        "session_type",
        "working_dir",
        "created_at",
        "updated_at",
        "archived_at",
        "parent_session_id",
    }
)
_MESSAGE_COLUMNS = frozenset(
    {"id", "message_id", "session_id", "role", "content_json", "created_timestamp"}
)
_SESSION_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,200}$")


class _DuplicateKey(ValueError):
    pass


def _object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
    value: dict[str, Any] = {}
    for key, item in pairs:
        if key in value:
            raise _DuplicateKey(key)
        value[key] = item
    return value


def _root_candidate(query: Query) -> str:
    if query.source_root:
        return query.source_root
    # Windows Goose Desktop/CLI uses %APPDATA%/Block/goose/data (upstream Paths).
    if os.name == "nt":
        appdata = os.environ.get("APPDATA")
        if appdata:
            return os.path.join(appdata, "Block", "goose", "data")
    data_home = os.environ.get("XDG_DATA_HOME")
    if data_home:
        return os.path.join(data_home, "goose")
    return os.path.expanduser("~/.local/share/goose")


def _existing_root(query: Query) -> str | None:
    candidate = _root_candidate(query)
    try:
        if not os.path.isdir(candidate):
            return None
        return canonical_root(candidate)
    except DiagnosticError:
        if query.source_root:
            raise
        return None


def _regular_file(path: str, root: str) -> bool:
    try:
        mode = os.lstat(path).st_mode
    except OSError:
        return False
    if stat.S_ISLNK(mode) or not stat.S_ISREG(mode):
        return False
    try:
        return is_within(path, root)
    except DiagnosticError:
        return False


def _database_path(root: str) -> str | None:
    path = os.path.join(root, DB_REL)
    return path if _regular_file(path, root) else None


def _stamp_sql(value: object) -> str | None:
    if value is None:
        return None
    if isinstance(value, (int, float)) and not isinstance(value, bool):
        number = float(value)
        if number > 10_000_000_000:
            number /= 1000.0
        try:
            return (
                datetime.fromtimestamp(number, timezone.utc)
                .isoformat(timespec="microseconds")
                .replace("+00:00", "Z")
            )
        except (OverflowError, OSError, ValueError):
            return None
    if isinstance(value, str) and value.strip():
        text = value.strip().replace(" ", "T", 1)
        if not text.endswith("Z") and "+" not in text[10:]:
            text = text + "Z" if "T" in text else text.replace(" ", "T") + "Z"
        try:
            return (
                datetime.fromisoformat(text.replace("Z", "+00:00"))
                .astimezone(timezone.utc)
                .isoformat(timespec="microseconds")
                .replace("+00:00", "Z")
            )
        except ValueError:
            return None
    return None


def _cwd_from_working_dir(value: object) -> str | None:
    if not isinstance(value, str) or not value.strip():
        return None
    try:
        return canonicalize_cwd(value)
    except DiagnosticError:
        return None


def _open_connection(database: str, root: str, budget: ReadBudget | None = None):
    limits = budget.limits if budget is not None else DEFAULT_BOUNDS
    try:
        size = os.path.getsize(database)
    except OSError as error:
        raise DiagnosticError.source_busy(provider=FORMAT_ID) from error
    if size > limits.sqlite_snapshot_bytes:
        return query_only_live_sqlite(database, root=root, provider=FORMAT_ID)
    return private_sqlite_connection(
        database, root=root, bounds=limits, provider=FORMAT_ID
    )


def _require_schema(connection: sqlite3.Connection) -> None:
    try:
        integrity = connection.execute("PRAGMA integrity_check(1)").fetchone()
        if integrity != ("ok",):
            raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID)
        tables = {
            row[0]
            for row in connection.execute(
                "SELECT name FROM sqlite_master WHERE type='table' "
                "AND name IN ('schema_version','sessions','messages','usage_ledger')"
            )
        }
        if tables != _REQUIRED_TABLES:
            raise DiagnosticError("E_UNSUPPORTED_FORMAT", source="goose", provider=FORMAT_ID)
        session_cols = {
            str(row[1]) for row in connection.execute("PRAGMA table_info(sessions)")
        }
        message_cols = {
            str(row[1]) for row in connection.execute("PRAGMA table_info(messages)")
        }
        if not _SESSION_COLUMNS.issubset(session_cols) or not _MESSAGE_COLUMNS.issubset(message_cols):
            raise DiagnosticError("E_UNSUPPORTED_FORMAT", source="goose", provider=FORMAT_ID)
        version_row = connection.execute(
            "SELECT MAX(version) FROM schema_version"
        ).fetchone()
        if version_row is None or version_row[0] is None or int(version_row[0]) != SCHEMA_VERSION:
            raise DiagnosticError("E_UNSUPPORTED_FORMAT", source="goose", provider=FORMAT_ID)
    except sqlite3.DatabaseError as error:
        raise DiagnosticError("E_UNSUPPORTED_FORMAT", source="goose", provider=FORMAT_ID) from error
    except (TypeError, ValueError) as error:
        raise DiagnosticError("E_UNSUPPORTED_FORMAT", source="goose", provider=FORMAT_ID) from error


def _exact_ref(value: str | None) -> str | None:
    if not value:
        return None
    text = value.strip()
    if not text or text == "latest":
        return None
    if _SESSION_ID_RE.fullmatch(text):
        return text
    return None


def _row_summary(
    *,
    database: str,
    session_id: str,
    name: object,
    working_dir: object,
    created_at: object,
    updated_at: object,
    query: Query,
    require_age: bool,
) -> SessionSummary | None:
    if not isinstance(session_id, str) or not session_id:
        return None
    cwd = _cwd_from_working_dir(working_dir)
    if query.cwd is not None:
        if cwd is None or not same_cwd(cwd, query.cwd):
            return None
    stamp = _stamp_sql(updated_at) or _stamp_sql(created_at)
    if require_age and not within_age(
        stamp, query.within_min, default_minutes=DEFAULT_BOUNDS.listing_age_minutes
    ):
        return None
    title = name if isinstance(name, str) and name.strip() else None
    return SessionSummary(
        source="goose",
        session_id=session_id,
        source_path=database,
        title=title,
        cwd=cwd,
        branch=None,
        created_at=_stamp_sql(created_at),
        updated_at=stamp,
        provider=FORMAT_ID,
        warnings=(),
    )


def _session_has_extractable_turn(
    connection: sqlite3.Connection,
    session_id: str,
    budget: ReadBudget,
) -> bool:
    """True when at least one public message yields non-empty text after decode.

    Corrupt ``content_json`` (malformed JSON / duplicate keys / bad types) raises
    ``E_CORRUPT_RECORD`` so latest selection cannot silently skip a newer broken
    session and hand back an older transcript (Codex P1).
    """

    row_limit = min(budget.limits.transcript_records, DEFAULT_BOUNDS.transcript_records)
    if row_limit <= 0:
        return False
    rows = connection.execute(
        """
        SELECT role, content_json
        FROM messages
        WHERE session_id = ?
          AND role IN ('user', 'assistant', 'tool', 'toolResult', 'tool_result', 'function')
        ORDER BY id ASC
        LIMIT ?
        """,
        (session_id, row_limit),
    )
    for role, content_json in rows:
        if not isinstance(role, str) or not isinstance(content_json, str):
            raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID)
        encoded = content_json.encode("utf-8")
        if len(encoded) > budget.limits.record_bytes:
            raise DiagnosticError.limit_exceeded()
        # Eligibility decoding counts toward the caller's scanned-record + source-byte budgets.
        budget.consume_bytes(len(encoded))
        budget.consume_records()
        text = _content_text(content_json)
        if text is not None and text.strip():
            return True
    return False


def _list_sessions(
    connection: sqlite3.Connection,
    *,
    database: str,
    query: Query,
    exact_id: str | None,
    budget: ReadBudget,
) -> list[SessionSummary]:
    scan_limit = min(budget.limits.scanned_records, DEFAULT_BOUNDS.scanned_records)
    list_limit = min(budget.limits.listed_sessions, DEFAULT_BOUNDS.listed_sessions)
    if exact_id is None and list_limit <= 0:
        return []

    def _fetch_exact(session_key: str) -> list[tuple]:
        return connection.execute(
            """
            SELECT id, name, session_type, working_dir, created_at, updated_at, archived_at
            FROM sessions
            WHERE id = ? OR lower(id) = lower(?)
            LIMIT 2
            """,
            (session_key, session_key),
        ).fetchall()

    def _fetch_normal() -> list[tuple]:
        # Cap candidates at scanned_records; do not fail when more exist — list
        # stops once list_limit eligible summaries are collected (Codex P2).
        if scan_limit <= 0:
            return []
        return connection.execute(
            """
            SELECT id, name, session_type, working_dir, created_at, updated_at, archived_at
            FROM sessions
            WHERE session_type = 'user'
              AND archived_at IS NULL
              AND EXISTS (
                SELECT 1 FROM messages m
                WHERE m.session_id = sessions.id
                  AND m.role IN ('user', 'assistant', 'tool')
              )
            ORDER BY updated_at DESC, id ASC
            LIMIT ?
            """,
            (scan_limit,),
        ).fetchall()

    if exact_id is not None:
        rows = _fetch_exact(exact_id)
        require_age = False
        # Exact miss falls back so free-text / title selection still works (Codex P2).
        if not rows:
            rows = _fetch_normal()
            require_age = True
            exact_id = None
            if list_limit <= 0:
                return []
    else:
        rows = _fetch_normal()
        require_age = True

    values: list[SessionSummary] = []
    for row in rows:
        session_id, name, session_type, working_dir, created_at, updated_at, archived_at = row
        if exact_id is None:
            if archived_at is not None:
                continue
            if isinstance(session_type, str) and session_type in _DEFAULT_EXCLUDE_TYPES:
                continue
            if not _session_has_extractable_turn(connection, session_id, budget):
                continue
        item = _row_summary(
            database=database,
            session_id=session_id,
            name=name,
            working_dir=working_dir,
            created_at=created_at,
            updated_at=updated_at,
            query=query,
            require_age=require_age,
        )
        if item is not None:
            # Guard zero listed_sessions before append (Codex P2).
            if exact_id is None and len(values) >= list_limit:
                break
            values.append(item)
            budget.consume_records()
            if exact_id is None and len(values) >= list_limit:
                break
    return values


def _content_text(content_json: str) -> str | None:
    try:
        payload = json.loads(content_json, object_pairs_hook=_object)
    except (json.JSONDecodeError, _DuplicateKey, RecursionError, UnicodeDecodeError) as error:
        raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID) from error
    if isinstance(payload, str) and payload.strip():
        return payload
    if not isinstance(payload, list):
        raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID)
    chunks: list[str] = []
    for part in payload:
        if isinstance(part, str) and part.strip():
            chunks.append(part)
            continue
        if not isinstance(part, Mapping):
            continue
        kind = part.get("type")
        if kind in {None, "text"}:
            text = part.get("text")
            if isinstance(text, str) and text.strip():
                chunks.append(text)
        elif kind in {"toolRequest", "toolResponse", "tool_use", "tool_result"}:
            # Keep only short text fields if present; skip raw binary/tool blobs.
            text = part.get("text")
            if isinstance(text, str) and text.strip():
                chunks.append(text)
    if not chunks:
        return None
    return "\n".join(chunks)


def _show_session(
    connection: sqlite3.Connection,
    *,
    database: str,
    session_id: str,
    query: Query,
    budget: ReadBudget,
) -> Session:
    rows = connection.execute(
        """
        SELECT id, name, working_dir, created_at, updated_at
        FROM sessions
        WHERE id = ?
        LIMIT 2
        """,
        (session_id,),
    ).fetchall()
    if len(rows) != 1:
        raise DiagnosticError("E_NO_MATCH", source="goose", provider=FORMAT_ID)
    _id, name, working_dir, created_at, updated_at = rows[0]
    cwd = _cwd_from_working_dir(working_dir)
    if query.cwd is not None and (cwd is None or not same_cwd(cwd, query.cwd)):
        raise DiagnosticError("E_NO_MATCH", source="goose", provider=FORMAT_ID)

    limit = budget.limits.transcript_records + 1
    cursor = connection.execute(
        """
        SELECT message_id, role, content_json, created_timestamp
        FROM messages
        WHERE session_id = ?
        ORDER BY id ASC
        LIMIT ?
        """,
        (session_id, limit),
    )
    turns: list[Turn] = []
    warnings: list[str] = []
    seen_message_ids: set[str] = set()
    turn_bounds = replace(DEFAULT_BOUNDS, tool_output_chars=query.max_tool_chars)
    count = 0
    try:
        for message_id, role, content_json, _created in cursor:
            count += 1
            if count > budget.limits.transcript_records:
                raise DiagnosticError.limit_exceeded()
            if not isinstance(role, str) or not isinstance(content_json, str):
                raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID)
            if isinstance(message_id, str) and message_id:
                if message_id in seen_message_ids:
                    raise DiagnosticError("E_CORRUPT_RECORD", source="goose", provider=FORMAT_ID)
                seen_message_ids.add(message_id)
            encoded = content_json.encode("utf-8")
            if len(encoded) > budget.limits.record_bytes:
                raise DiagnosticError.limit_exceeded()
            # Charge the shared ReadBudget so reused/partial budgets fail closed (Codex P2).
            budget.consume_transcript_records()
            budget.consume_bytes(len(encoded))
            mapped_role = role
            if role in {"toolResult", "tool_result", "function"}:
                mapped_role = "tool"
            if mapped_role not in {"user", "assistant", "tool"}:
                continue
            text = _content_text(content_json)
            if text is None:
                continue
            turn, turn_warnings = sanitize_turn_record(
                {"role": mapped_role, "content": text},
                ordinal=len(turns),
                bounds=turn_bounds,
            )
            warnings.extend(turn_warnings)
            if turn is not None:
                budget.consume_turns()
                turns.append(turn)
    finally:
        cursor.close()

    last_user = next((t.content for t in reversed(turns) if t.role == "user"), None)
    last_assistant = next((t.content for t in reversed(turns) if t.role == "assistant"), None)
    title = name if isinstance(name, str) and name.strip() else None
    return Session(
        source="goose",
        session_id=session_id,
        source_path=database,
        title=title,
        cwd=cwd,
        branch=None,
        created_at=_stamp_sql(created_at),
        updated_at=_stamp_sql(updated_at) or _stamp_sql(created_at),
        last_user_request=last_user,
        last_assistant_action=last_assistant,
        turns=tuple(turns),
        warnings=tuple(dict.fromkeys(warnings)),
    )


class GooseAdapter:
    key = "goose"

    def approved_roots(self, query: Query) -> tuple[str, ...]:
        root = _existing_root(query)
        return (root,) if root else ()

    def probe(self, query: Query) -> CapabilityReport:
        try:
            root = _existing_root(query)
            if root is None:
                return CapabilityReport(self.key, FORMAT_ID, "unavailable")
            database = _database_path(root)
            if database is None:
                sessions = os.path.join(root, "sessions")
                if os.path.isdir(sessions) and not os.path.islink(sessions):
                    return CapabilityReport(self.key, FORMAT_ID, "unsupported", root=root)
                return CapabilityReport(self.key, FORMAT_ID, "unavailable", root=root)
            try:
                with _open_connection(database, root) as connection:
                    _require_schema(connection)
            except DiagnosticError as error:
                if error.code in {"E_UNSAFE_PATH", "E_SOURCE_BUSY", "E_SQLITE_LIVE_WAL"}:
                    return CapabilityReport(self.key, FORMAT_ID, "unsafe", root=root)
                if error.code in {"E_UNSUPPORTED_FORMAT", "E_CORRUPT_RECORD"}:
                    return CapabilityReport(self.key, FORMAT_ID, "unsupported", root=root)
                raise
            return CapabilityReport(
                self.key, FORMAT_ID, "supported", root=root, evidence=(FORMAT_ID,)
            )
        except DiagnosticError as error:
            state = "unsafe" if error.code in {"E_UNSAFE_PATH", "E_SOURCE_BUSY", "E_SQLITE_LIVE_WAL"} else "unsupported"
            return CapabilityReport(self.key, FORMAT_ID, state)

    def list(self, query: Query, budget: ReadBudget) -> list[SessionSummary]:
        root = _existing_root(query)
        if root is None:
            raise DiagnosticError("E_CAPABILITY_UNAVAILABLE", source=self.key, provider=FORMAT_ID)
        database = _database_path(root)
        if database is None:
            raise DiagnosticError("E_CAPABILITY_UNAVAILABLE", source=self.key, provider=FORMAT_ID)
        exact = _exact_ref(query.ref)
        list_query = query
        if exact is not None and query.within_min is None:
            list_query = Query(
                source=query.source,
                ref=query.ref,
                cwd=query.cwd,
                within_min=0,
                source_root=query.source_root,
                max_tool_chars=query.max_tool_chars,
            )
        with _open_connection(database, root, budget) as connection:
            _require_schema(connection)
            values = _list_sessions(
                connection,
                database=database,
                query=list_query,
                exact_id=exact,
                budget=budget,
            )
        values.sort(key=lambda item: item.session_id)
        values.sort(key=lambda item: item.updated_at or "", reverse=True)
        values.sort(key=lambda item: item.updated_at is None)
        if exact is not None:
            return values
        list_limit = min(budget.limits.listed_sessions, DEFAULT_BOUNDS.listed_sessions)
        return values[:list_limit]

    def show(self, ref: ResolvedRef, query: Query, budget: ReadBudget) -> Session:
        root = _existing_root(query)
        if root is None:
            raise DiagnosticError("E_CAPABILITY_UNAVAILABLE", source=self.key, provider=FORMAT_ID)
        database = ref.source_path or _database_path(root)
        if database is None or not _regular_file(database, root):
            raise DiagnosticError.unsafe_path()
        session_id = ref.session_id
        with _open_connection(database, root, budget) as connection:
            _require_schema(connection)
            return _show_session(
                connection,
                database=database,
                session_id=session_id,
                query=query,
                budget=budget,
            )


ADAPTER = GooseAdapter()

SHA-256: 671187b7492465f562778e2a336d3bc012f71e3659006cd2352016396b6a04ec