@sema-agent/core
Version:
Stateless, task-oriented AI agent core
98 lines (97 loc) • 5.66 kB
TypeScript
import type { Message } from "../../internal/llm.js";
/**
* design/152 S4b D-2 — READ-ONLY adapter over the Claude Code subagent transcript sidecars
* (`~/.claude/projects/<proj>/<sid>/subagents/agent-<id>.jsonl` + companion
* `agent-<id>.meta.json`), so a sema engine taking over a CC-created team (D-3: sequential
* handover) can enumerate the teammates' transcript sidecars and project them into sema message
* shapes for resume context.
*
* D-2 ruling (design/152 §3): the sidecar is CC→sema ONE-WAY. sema's transcript truth source
* is the SessionStore — this module never writes, and nothing here mints a second truth source
* (the dual-truth-source pathology design/151 just eliminated stays eliminated). Dual-write is a
* separate ruling gated on a real user need.
*
* Disk shape (cc-teams-diskform README):
* - flat sidecars: `subagents/agent-<id>.jsonl`
* - nested children: `subagents/<parentSeg>/agent-<id>.jsonl` (parent-segment subdirectories)
* - workflow runs: `subagents/workflows/<runId>/agent-<t>.jsonl`
* - remote agents: `subagents/remote-agents/...` (CC's own migration walks the three siblings;
* we walk EVERY subdirectory — symlinks are not followed)
* - companion meta: `agent-<id>.meta.json` = `{agentType, description, name, toolUseId,
* spawnDepth}` (revival lookup keys; same family as BackgroundAgentRecord's meta-sidecar parity
* fields). Absent meta is tolerated (fail-soft) — the jsonl alone still enumerates.
*
* Line container = the CC session-entry envelope (same container as the main-session transcript):
* `{type:"user"|"assistant", message:{...}, uuid, parentUuid, timestamp, ...}` interleaved with
* attachment/marker lines that carry no `message`. Projection keeps ONLY the message-bearing
* user/assistant lines; everything else is counted, never silently vanished.
*
* Locking: none. CC appends whole lines; reads tolerate a torn trailing line as one malformed line
* (counted). D-3 promises sequential handover only — a concurrently-writing CC process is outside
* the supported envelope, and read-side locks would not widen it.
*/
/** Companion `agent-<id>.meta.json` shape (loose — unknown keys ride through verbatim). */
export interface CcSidecarMeta extends Record<string, unknown> {
agentType?: string;
description?: string;
name?: string;
toolUseId?: string;
spawnDepth?: number;
}
/** One enumerated sidecar transcript (jsonl + optional companion meta). */
export interface CcSidecarHandle {
/** The `<id>` from the `agent-<id>.jsonl` filename. */
agentId: string;
/** Absolute path to the transcript jsonl. */
jsonlPath: string;
/** Directory segments under `subagents/` (nesting/workflows/remote-agents); flat sidecar = []. */
segments: string[];
/** Parsed companion meta; `undefined` when the meta file is absent or unparseable (fail-soft). */
meta?: CcSidecarMeta;
}
/**
* Enumerate every transcript sidecar under a CC `subagents/` directory, walking nested
* parent-segment subdirectories (symlinks not followed). Missing directory ⇒ `[]` — the CC tree is
* a PARTIAL tree (headless runs write sidecars but a REPL-only subtree may be absent entirely), so
* absence is a normal state, never a throw.
* Order is deterministic: lexicographic by (segments join, agentId).
*/
export declare function listCcSidecarAgents(subagentsDir: string): CcSidecarHandle[];
/** One parsed jsonl line, verbatim (the raw CC session-entry envelope). */
export interface CcSidecarLine {
raw: Record<string, unknown>;
}
/** A parsed sidecar transcript: verbatim lines + an honest malformed-line count. */
export interface CcSidecarTranscript {
lines: CcSidecarLine[];
/** Lines that failed JSON.parse or were not objects (e.g. a torn trailing append). */
malformedLines: number;
}
export declare function readCcSidecarTranscript(jsonlPath: string): CcSidecarTranscript;
/** Projection result: sema-shaped messages + honest counts for what was NOT projected. */
export interface CcSidecarMessages {
messages: Message[];
/** Envelope lines without a projectable user/assistant message (attachments, markers, …). */
skippedLines: number;
}
/**
* Project parsed sidecar lines into sema message shapes for resume context.
*
* Mapping (CC session-entry → sema):
* - `type:"user"`, string content → {@link UserMessage} verbatim.
* - `type:"user"`, array content → contiguous `text`/`image` runs fold into
* {@link UserMessage}s and `tool_result` blocks become {@link ToolResultMessage} rows, in DISK
* BLOCK ORDER (toolName recovered from the preceding assistant `tool_use` id — "unknown" when
* the pairing is outside the file; base64 image sources map to sema ImageContent).
* - `type:"assistant"` → {@link AssistantMessage}; `text`/`thinking`/
* `redacted_thinking`/`tool_use` blocks map to their sema block shapes; `api:"anthropic-messages"`,
* `provider:"claude-code"` (an honest origin label — this transcript was produced by CC, not by
* a sema brain), model/usage/stop_reason carried over (`cost` unpriced-zero).
* - anything else (attachment lines, marker lines, unknown block types) → counted in
* `skippedLines`, never a throw and never a silent drop.
*/
export declare function ccSidecarToMessages(lines: CcSidecarLine[]): CcSidecarMessages;
/** Convenience: enumerate + parse + project one sidecar in a single read-only call. */
export declare function readCcSidecarMessages(handle: CcSidecarHandle): CcSidecarMessages & {
malformedLines: number;
};