chat: tag OpenCode sessions with officer metadata (cwd) + filter lists by it
OpenCode has no per-session directory (every session runs in the fixed server's cwd),
so sessions from every context (/chat pwd, email account, project) all landed in one
list. Now each session is tagged on creation with its logical cwd via the free-form
session `metadata`: { officer: { cwd } } — API-settable, round-trips on list+detail,
never touched by opencode core (confirmed by source dive + live test).
- client.createSession(metadata?) sends `metadata`; adds OfficerSessionMeta + officerMeta() helper.
- send-opencode tags new sessions with { officer: { cwd } } (the resolved chat cwd).
- websocket: pass the full resolved cwd for every context (not just non-/chat).
- listOpenCodeSessions(cwd?) filters by metadata.officer.cwd; chat.ts passes the request cwd.
Verified live: sessions tagged with distinct cwds list only under their own cwd.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { ClaudeSessionSummary, ClaudeSessionDetail, ClaudeChatMessage } from './claude-sessions';
|
||||
import { ensureServer } from './opencode/server-manager';
|
||||
import { getConnection } from './opencode/client';
|
||||
import { getConnection, officerMeta } from './opencode/client';
|
||||
import { logger } from './logger';
|
||||
|
||||
// The OpenCode analog of claude-sessions.ts. OpenCode's own store is the source of truth, read via the
|
||||
@@ -8,24 +8,30 @@ import { logger } from './logger';
|
||||
// one server's project, so listing is just GET /session. Returns the same shapes as the Claude reader,
|
||||
// tagged harness:'opencode', so chat.ts can merge both harnesses transparently.
|
||||
|
||||
/** List OpenCode sessions. Never throws — returns [] if the server is unavailable. */
|
||||
export async function listOpenCodeSessions(): Promise<ClaudeSessionSummary[]> {
|
||||
/**
|
||||
* List OpenCode sessions tagged with the given logical cwd (via `metadata.officer.cwd`), so each
|
||||
* context (/chat pwd, email account, project) sees only its own. With no cwd, returns all. Never
|
||||
* throws — returns [] if the server is unavailable.
|
||||
*/
|
||||
export async function listOpenCodeSessions(cwd?: string): Promise<ClaudeSessionSummary[]> {
|
||||
try {
|
||||
const { baseUrl } = await ensureServer();
|
||||
const sessions = await getConnection(baseUrl).listSessions();
|
||||
return sessions.map((s) => {
|
||||
const created = s.time?.created ?? Date.now();
|
||||
const updated = s.time?.updated ?? created;
|
||||
return {
|
||||
id: s.id,
|
||||
title: s.title || '(untitled)',
|
||||
cwd: s.location?.directory ?? '',
|
||||
createdAt: new Date(created).toISOString(),
|
||||
updatedAt: new Date(updated).toISOString(),
|
||||
messageCount: 0, // the session list endpoint doesn't include a turn count
|
||||
harness: 'opencode',
|
||||
} satisfies ClaudeSessionSummary;
|
||||
});
|
||||
return sessions
|
||||
.filter((s) => !cwd || officerMeta(s.metadata).cwd === cwd)
|
||||
.map((s) => {
|
||||
const created = s.time?.created ?? Date.now();
|
||||
const updated = s.time?.updated ?? created;
|
||||
return {
|
||||
id: s.id,
|
||||
title: s.title || '(untitled)',
|
||||
cwd: s.location?.directory ?? '',
|
||||
createdAt: new Date(created).toISOString(),
|
||||
updatedAt: new Date(updated).toISOString(),
|
||||
messageCount: 0, // the session list endpoint doesn't include a turn count
|
||||
harness: 'opencode',
|
||||
} satisfies ClaudeSessionSummary;
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('Failed to list OpenCode sessions', { error: String(err) });
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user