feat: bind bot chats to existing harness sessions

This commit is contained in:
xmanrui 2026-08-18 02:59:29 +08:00
parent 76d5430f1f
commit 1af8610cd5
21 changed files with 1807 additions and 130 deletions

View file

@ -0,0 +1,110 @@
import { isAbsolute } from 'node:path';
const MAX_SESSION_ID_LENGTH = 256;
const UNSAFE_SESSION_ID = /[\p{White_Space}\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
const UNSAFE_WORKSPACE_PATH = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
function bindingError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
function validatedSessionId(value) {
if (typeof value !== 'string' || !value || value.length > MAX_SESSION_ID_LENGTH
|| UNSAFE_SESSION_ID.test(value)) {
throw bindingError('session-id-invalid', 'A non-empty, safe session id is required');
}
return value;
}
function sessionWorkspace(sessionId, value) {
if (!Array.isArray(value?.items) || !Array.isArray(value?.archivedSessionIds)
|| value.archivedSessionIds.some((id) => typeof id !== 'string' || !id)) {
throw new Error('Harness returned an invalid response for workspace.list');
}
const owners = [];
for (const workspace of value.items) {
if (typeof workspace?.workspaceId !== 'string' || !workspace.workspaceId
|| typeof workspace.path !== 'string' || !isAbsolute(workspace.path)
|| !Array.isArray(workspace.sessionIds)
|| workspace.sessionIds.some((id) => typeof id !== 'string' || !id)) {
throw new Error('Harness returned an invalid response for workspace.list');
}
for (const accountedId of workspace.sessionIds) {
if (accountedId === sessionId) owners.push(workspace);
}
}
if (owners.length === 0) {
throw bindingError('session-not-registered', 'The session is not registered to a Harness workspace');
}
if (owners.length !== 1) {
throw bindingError(
'session-workspace-ambiguous',
'The session is registered to more than one Harness workspace',
);
}
if (UNSAFE_WORKSPACE_PATH.test(owners[0].path)) {
throw new Error('Harness returned an unsafe workspace path for the session');
}
return {
workspace: owners[0],
archived: value.archivedSessionIds.includes(sessionId),
};
}
function sessionSummary(sessionId, value) {
if (!Array.isArray(value?.items)
|| value.items.some((item) => typeof item?.sessionId !== 'string' || !item.sessionId)) {
throw new Error('Harness returned an invalid response for session.list');
}
const matches = value.items.filter((item) => item.sessionId === sessionId);
if (matches.length === 0) {
throw bindingError('session-summary-unavailable', 'The session is no longer available from Harness');
}
if (matches.length !== 1) {
throw new Error('Harness returned duplicate session summaries for session.list');
}
const [summary] = matches;
if (summary.origin === 'subagent') {
throw bindingError(
'session-subagent-unsupported',
'Subagent sessions cannot be adopted as a bot conversation',
);
}
if (summary.origin !== undefined) {
throw new Error('Harness returned an invalid session origin for session.list');
}
const title = summary.projections?.values?.title;
if (title !== undefined && title !== null && typeof title !== 'string') {
throw new Error('Harness returned an invalid session title for session.list');
}
return { title: typeof title === 'string' ? title : null };
}
export async function adoptRegisteredWorkspaceSession(client, value, options = {}, timeoutMs = 30_000) {
const sessionId = validatedSessionId(value);
await client.ensureRunning(options);
const workspaceList = await client.rpc('workspace.list', {}, timeoutMs, options);
const { workspace, archived } = sessionWorkspace(sessionId, workspaceList);
const summary = sessionSummary(
sessionId,
await client.rpc('session.list', {}, timeoutMs, options),
);
const adopted = await client.rpc('session.create', {
workspaceId: workspace.workspaceId,
sessionId,
}, timeoutMs, options);
if (!adopted || adopted.sessionId !== sessionId) {
throw new Error('Harness returned an invalid response for session.create');
}
return {
sessionId,
workspace: workspace.path,
title: summary.title,
archived,
};
}