Agent session sources

A session source reports every live coding-agent session of its harnesses on the server's machine, however it was started. Terminay decides which project and terminal each session belongs to.

Manifest contribution

JSON
"permissions": ["agent-observation"],
"contributes": {
  "agentSessionSources": [{
    "id": "com.example.agent/sessions",
    "displayName": "Example Agent",
    "harnesses": [{ "id": "example-agent", "displayName": "Example Agent" }],
    "environmentVariables": ["EXAMPLE_AGENT_HOME"]
  }]
}

A harness is one agent the source can report, such as Claude Code or Codex. Each declared harness gets its own switch under the extension in Settings → Extensions. Field rules are in the manifest reference.

Runtime contract

Register with context.agents.registerSessionSource(sourceId, runtime). defineSessionSource() is an identity helper that types the runtime.

TypeScript
interface AgentSessionSourceRuntime {
  start(start: AgentSessionSourceStart): void | Promise<void>;
}

interface AgentSessionSourceStart {
  enabledHarnesses: readonly string[];
  publisher: AgentSessionPublisher;
  signal: AbortSignal;
  onEnabledHarnessesChanged(
    listener: (enabledHarnesses: readonly string[]) => void,
  ): Disposable;
}

interface AgentSessionPublisher {
  reset(sessions: readonly AgentSessionSnapshot[]): void;
  upsert(session: AgentSessionSnapshot): void;
  remove(sessionId: string): void;
  diagnostic(diagnostic: AgentSessionSourceDiagnostic): void;
}
MemberMeaning
start()Begins watching. Resolve once the source is running; keep publishing until signal aborts.
enabledHarnessesDeclared harness IDs currently switched on.
onEnabledHarnessesChangedDelivers the new enabled set when a user switches a harness, without restarting the extension.
signalAborts when the source is disposed, the extension is disabled, or agent status is switched off.

Publication rules

  • Ordering. Calls take effect in the order made. reset replaces everything the source reported before, upsert replaces one session by ID, and remove forgets one.
  • Resets. Send a reset after starting and after every enabled-harness change. A reset holds at most 1,024 sessions and never names one session twice; report more with upserts.
  • Harness switches. A snapshot naming an undeclared or switched-off harness is rejected. Stop reporting a harness when it is switched off and report its live sessions when it is switched back on.
  • Cancellation. When signal aborts, release every watch. Later publisher calls are ignored.
  • Watching. Watch files and processes; do not poll.

Session snapshot

TypeScript
publisher.upsert({
  id: 'sess-1',
  harness: 'example-agent',
  pid: 101,
  cwd: '/work/app',
  title: 'Fix tests',
  model: 'example-large',
  status: 'waiting',
  waitingFor: 'Approve shell command',
  lastTurn: 'completed',
  lastTurnEndedAt: Date.now(),
  subagents: [{ id: 'sub-1', type: 'explore', status: 'running' }],
});

AgentSessionSnapshot is a closed object. Unknown fields are rejected. Times are epoch milliseconds.

FieldRequiredMeaning and bound
idYesSource-scoped and stable for the conversation. At most 256 characters.
harnessYesA declared harness ID that is switched on.
pidYesThe live process holding the session. A positive integer.
cwdYesAbsolute working directory, starting with /. At most 4,096 characters.
titleNoAt most 512 characters.
modelNoAt most 256 characters.
statusNorunning, waiting, blocked, or idle.
waitingForNoWhat a waiting or blocked session waits for, such as a tool approval. At most 1,024 characters.
toolNoName of the tool currently running. At most 256 characters.
lastTurnNocompleted, failed, or interrupted.
lastTurnEndedAtNoWhen the last turn ended.
errorNoBounded, redacted message for a failed turn. At most 1,024 characters.
subagentsNoUp to 64 entries with unique IDs: id, optional parentId, type (at most 128 characters), optional title, and status of running, completed, failed, or cancelled.

The bounds are exported as EXTENSION_LIMITS. Use boundedAgentText(value, maximum) to shorten display text such as titles and messages. Never shorten identity values; an over-long ID is rejected.

Transcripts, prompts beyond the title, tool arguments, and raw records are never accepted.

Diagnostics

publisher.diagnostic({ code, message }) records a typed problem. code is lowercase kebab-case, at most 64 characters, such as watch-failed. message is at most 512 characters and must carry no paths or conversation content.

What Terminay owns

  • Project. A session appears in a project's Agents panel when its cwd is the project root or below it, or inside a linked Git worktree of the project's repository.
  • Terminal. A session whose process descends from a Terminay terminal is bound to that terminal. Any other session is shown as External. An extension cannot claim a terminal.
  • Everything else. Acknowledgement, ordering, and presentation.

Validation errors

validateAgentSessionSnapshot, validateAgentSessionReset, and validateAgentSessionSourceDiagnostic apply the host's rules and return issues with codes such as invalid_string, invalid_array, invalid_enum, invalid_path, invalid_integer, invalid_id, unknown_field, duplicate, and harness_not_enabled.

See the session source example for a complete package.