Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
65e141d
ported over runtime & server
icehaunter Apr 22, 2026
031165c
Added correct docker builds for the server
icehaunter Apr 22, 2026
2fd82f3
ported over the server UI, and packaged it into docker
icehaunter Apr 22, 2026
c39dcb0
added the CLI
icehaunter Apr 23, 2026
1f54c87
split out the horton runner
icehaunter Apr 23, 2026
c57e05c
fixing tests
icehaunter Apr 23, 2026
14801c5
mass rename
icehaunter Apr 23, 2026
550e555
add coding-session entity (Claude Code / Codex runner)
kevin-dp Apr 23, 2026
42b8c63
fix coding-session: fs-based session discovery + init guard
balegas Apr 23, 2026
f107614
send initial message on coding-session spawn to trigger handler
balegas Apr 23, 2026
23358a1
feat(agents-server-ui): add state explorer panel to entity view
balegas Apr 23, 2026
1dc66a6
fix(agents-server-ui): improve state explorer layout and fix CI
balegas Apr 23, 2026
1727ea8
fix(agents-server-ui): remove horizontal scrollbar overflow from chat…
balegas Apr 23, 2026
ab6b555
fix(agents-server-ui): fix chat content overflow when state explorer …
balegas Apr 23, 2026
75af364
fix(agents-server-ui): fix timeline rendering when toggling state exp…
balegas Apr 23, 2026
43aebbe
feat(agents-server-ui): add jump-to-bottom button and unify entity la…
balegas Apr 24, 2026
759ed3e
feat(agents-server-ui): add jump-to-bottom button to coding session t…
balegas Apr 24, 2026
0ae4c26
fix(agents-server-ui): center jump-to-bottom button and use neutral c…
balegas Apr 24, 2026
4815b0b
fix(agents-server-ui): make jump-to-bottom button opaque, remove shadow
balegas Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/agents-runtime/src/context-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ import { runtimeLog } from './log'
import { sliceChars } from './token-budget'
import { createContextTools } from './tools/context-tools'
import { CACHE_TIERS } from './types'
import {
CODING_SESSION_ENTITY_TYPE,
codingSessionEntityUrl,
} from './observation-sources'
import type { ChangeEvent } from '@durable-streams/state'
import type {
AgentConfig,
AgentHandle,
AgentRunResult,
AgentTool,
CodingSessionEventRow,
CodingSessionHandle,
CodingSessionMeta,
CodingSessionStatus,
EntityHandle,
EntityStreamDBWithActions,
HandlerContext,
Expand All @@ -28,6 +36,7 @@ import type {
SharedStateSchemaMap,
StateProxy,
TimelineProjectionOpts,
UseCodingAgentOptions,
UseContextConfig,
Wake,
WakeEvent,
Expand Down Expand Up @@ -510,6 +519,75 @@ export function createHandlerContext<TState extends StateProxy = StateProxy>(
): SharedStateHandle<TSchema> {
return config.doMkdb(id, schema)
},
async useCodingAgent(
sessionId: string,
opts: UseCodingAgentOptions
): Promise<CodingSessionHandle> {
const spawnArgs: Record<string, unknown> = { agent: opts.agent }
if (opts.cwd !== undefined) spawnArgs.cwd = opts.cwd
if (opts.nativeSessionId !== undefined) {
spawnArgs.nativeSessionId = opts.nativeSessionId
}
if (opts.importFrom !== undefined) {
spawnArgs.importFrom = opts.importFrom
}

const spawnOpts: {
observe: true
wake: Wake
initialMessage?: unknown
} = {
observe: true,
wake: opts.wake ?? `runFinished`,
}

const entityHandle = await config.doSpawn(
CODING_SESSION_ENTITY_TYPE,
sessionId,
spawnArgs,
spawnOpts
)

const entityUrl = codingSessionEntityUrl(sessionId)
const readEvents = (): Array<CodingSessionEventRow> => {
const collection = entityHandle.db?.collections.events
if (!collection) return []
const rows = (collection as { toArray?: unknown }).toArray
return (Array.isArray(rows) ? rows : []) as Array<CodingSessionEventRow>
}
const readMeta = (): CodingSessionMeta | undefined => {
const collection = entityHandle.db?.collections.sessionMeta
if (!collection) return undefined
const row = (collection as { get?: (k: string) => unknown }).get?.(
`current`
)
return row as CodingSessionMeta | undefined
}
const MESSAGE_TYPES = new Set([`user_message`, `assistant_message`])

const handle: CodingSessionHandle = {
entityUrl,
sessionId,
agent: opts.agent,
run: entityHandle.run,
meta: readMeta,
status: (): CodingSessionStatus | undefined => readMeta()?.status,
send: (prompt: string): void => {
config.executeSend({
targetUrl: entityUrl,
payload: { text: prompt },
type: `prompt`,
})
},
get events(): ReadonlyArray<CodingSessionEventRow> {
return readEvents()
},
get messages(): ReadonlyArray<CodingSessionEventRow> {
return readEvents().filter((e) => MESSAGE_TYPES.has(e.type))
},
}
return handle
},
send(
entityUrl: string,
payload: unknown,
Expand Down
17 changes: 16 additions & 1 deletion packages/agents-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ export type {
SharedStateSchemaMap,
SharedStateHandle,
AgentConfig,
CodingAgentType,
CodingSessionEventRow,
CodingSessionHandle,
CodingSessionMeta,
CodingSessionStatus,
EntityDefinition,
EntityTypeEntry,
SharedStateHandleInfo,
SpawnHandleInfo,
UseCodingAgentOptions,
WakePhase,
WakeSession,
EntityHandle,
Expand Down Expand Up @@ -174,7 +180,16 @@ export {
manifestSourceKey,
} from './manifest-helpers'

export { entity, cron, entities, tagged, db } from './observation-sources'
export {
CODING_SESSION_ENTITY_TYPE,
codingSession,
codingSessionEntityUrl,
entity,
cron,
entities,
tagged,
db,
} from './observation-sources'
export type {
EntityObservationSource,
CronObservationSource,
Expand Down
16 changes: 16 additions & 0 deletions packages/agents-runtime/src/observation-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ export function entity(entityUrl: string): EntityObservationSource {
}
}

/** Entity type name for the built-in coding-session entity. */
export const CODING_SESSION_ENTITY_TYPE = `coding-session`

export function codingSessionEntityUrl(sessionId: string): string {
return `/${CODING_SESSION_ENTITY_TYPE}/${sessionId}`
}

/**
* Observation source for a coding-session entity (Claude Code / Codex
* CLI session). Sugar for `entity(codingSessionEntityUrl(sessionId))`
* so callers don't have to hard-code the type-name prefix.
*/
export function codingSession(sessionId: string): EntityObservationSource {
return entity(codingSessionEntityUrl(sessionId))
}

export function cron(
expression: string,
opts?: { timezone?: string }
Expand Down
93 changes: 93 additions & 0 deletions packages/agents-runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,88 @@ export type AgentRunResult = {

export type AgentTool = PiAgentTool

/**
* Which CLI backs a `coding-session` entity. The same normalized event
* shape is written regardless.
*/
export type CodingAgentType = `claude` | `codex`

export type CodingSessionStatus = `initializing` | `idle` | `running` | `error`

/**
* One row in a coding-session entity's `events` collection — the mirror
* of an `agent-session-protocol` NormalizedEvent. `payload` holds the
* original event as returned by `loadSession` / `tailSession`.
*/
export interface CodingSessionEventRow {
key: string
ts: number
type: string
callId?: string
payload: Record<string, unknown>
}

export interface CodingSessionMeta {
/** Electric-side session id (matches the spawn id passed to useCodingAgent). */
electricSessionId: string
/** Native session id assigned by the CLI. Populated after the first CLI invocation. */
nativeSessionId?: string
agent: CodingAgentType
cwd?: string
status: CodingSessionStatus
error?: string
/** Inbox key of the prompt currently running, when status === `running`. */
currentPromptInboxKey?: string
}

export interface UseCodingAgentOptions {
agent: CodingAgentType
/**
* Attach to an existing local session by native id (as written to
* `~/.claude/projects/...` or `~/.codex/sessions/...`). When omitted,
* a fresh session is created on the first prompt.
*/
nativeSessionId?: string
/**
* Import an existing local session into a fresh session of `agent`.
* Same-agent imports are lossless (native rewrite); cross-agent
* imports round-trip through the normalized event stream.
*/
importFrom?: {
agent: CodingAgentType
sessionId: string
}
/** Working directory the CLI runs in. Defaults to the runtime's cwd. */
cwd?: string
/**
* Wake policy for the caller observing this session. Defaults to
* `"runFinished"` — the caller wakes each time the session entity
* finishes a prompt. Pass a `{ on: "change", ... }` wake to stream
* per-event updates.
*/
wake?: Wake
}

export interface CodingSessionHandle {
/** Electric entity URL backing this session (e.g. `/coding-session/<id>`). */
readonly entityUrl: string
/** Electric-side session id (the id passed to `ctx.useCodingAgent`). */
readonly sessionId: string
readonly agent: CodingAgentType
/** Current metadata, or undefined until the session entity has initialized. */
meta: () => CodingSessionMeta | undefined
/** Shortcut for `meta()?.status`. */
status: () => CodingSessionStatus | undefined
/** Resolves when the session entity finishes its current run. */
run: Promise<void>
/** Queue a prompt. Prompts run serially on the session. */
send: (prompt: string) => void
/** Live view of normalized session events as rows. */
readonly events: ReadonlyArray<CodingSessionEventRow>
/** Live filtered view — only `user_message` and `assistant_message` rows. */
readonly messages: ReadonlyArray<CodingSessionEventRow>
}

export interface AgentConfig {
systemPrompt: string
model: string
Expand Down Expand Up @@ -656,6 +738,17 @@ export interface HandlerContext<TState extends StateProxy = StateProxy> {
id: string,
schema: T
) => SharedStateHandle<T>
/**
* Spawn-or-attach a `coding-session` entity that runs a Claude Code or
* Codex CLI session, and return a typed handle for prompting it and
* observing its normalized event stream. Requires
* `registerCodingSession` to have been called on the runtime's
* registry.
*/
useCodingAgent: (
sessionId: string,
opts: UseCodingAgentOptions
) => Promise<CodingSessionHandle>
send: (
entityUrl: string,
payload: unknown,
Expand Down
3 changes: 3 additions & 0 deletions packages/agents-server-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@durable-streams/client": "npm:@electric-ax/durable-streams-client-beta@^0.3.0",
"@durable-streams/state": "npm:@electric-ax/durable-streams-state-beta@^0.3.0",
"@electric-ax/agents-runtime": "workspace:*",
"@radix-ui/themes": "^3.3.0",
"@tanstack/react-table": "^8.21.3",
"@tanstack/db": "^0.6.4",
"@tanstack/electric-db-collection": "^0.3.2",
"@tanstack/react-db": "^0.1.82",
Expand Down
Loading
Loading