@norman-else/dsh-claude 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +55 -55
- package/LICENSE +21 -21
- package/README.md +82 -191
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +0 -0
- package/lib/bin.mjs.map +1 -1
- package/lib/client.js +439 -183
- package/lib/client.js.map +1 -1
- package/lib/command-bridge-C10-lz6A.mjs.map +1 -1
- package/lib/events-BdDs9ebF.mjs.map +1 -1
- package/lib/index.d.mts +33 -0
- package/lib/index.mjs +292 -31
- package/lib/index.mjs.map +1 -1
- package/lib/preset-installer-DMANIjwu.mjs.map +1 -1
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +181 -181
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-bridge-C10-lz6A.mjs","names":[],"sources":["../src/command-bridge.ts"],"sourcesContent":["import type { SlashCommand } from '@anthropic-ai/claude-agent-sdk'\
|
|
1
|
+
{"version":3,"file":"command-bridge-C10-lz6A.mjs","names":[],"sources":["../src/command-bridge.ts"],"sourcesContent":["import type { SlashCommand } from '@anthropic-ai/claude-agent-sdk'\nimport type { CommandDescriptor } from '@deepseek-ai/dsh-commands'\nimport { redactText } from './events.ts'\n\nconst COMMAND_NAME = /^[a-z][a-z0-9_-]*$/u\nconst MAX_DESCRIPTION_CHARS = 300\nconst MAX_HINT_CHARS = 120\n\n/** DSH registry names reject `:`, but plugin-qualified Claude skills carry it\n * (e.g. `awesome-skills:ci-deploy`). Derive a registry-safe public name while\n * forwarding keeps the exact Claude-side name. */\nfunction registryName(claudeName: string): string | undefined {\n if (COMMAND_NAME.test(claudeName)) return claudeName\n const short = claudeName.includes(':') ? claudeName.slice(claudeName.lastIndexOf(':') + 1) : undefined\n if (short !== undefined && COMMAND_NAME.test(short)) return short\n const sanitized = claudeName.replaceAll(':', '-').toLowerCase()\n return COMMAND_NAME.test(sanitized) ? sanitized : undefined\n}\n\n/**\n * Names registered as CLIENT-side commandUi contributions (not host\n * commands). The DSH web command palette fails its whole command group when\n * a host command name equals a contribution name\n * (\"contribution /model collides with a host command\"), so these can never\n * be registered by this bridge. Keep in sync with the client bundles:\n * dsh-client-ui-model-selection registers \"model\".\n */\nconst CLIENT_CONTRIBUTION_NAMES: ReadonlySet<string> = new Set(['model'])\n\nexport interface ClaudeCommandTarget {\n list(): readonly Pick<CommandDescriptor, 'name'>[]\n}\n\n/**\n * Agent-scope command-directory contract. The commands service is unreachable\n * from the host plugin's view of `agent.ctx` (\"without inject\"), but the\n * preset route plugin runs inside the agent's preset composition with\n * `commands` injected. The Host reads only the exact agent's effective names\n * for collision checks; Claude catalog entries are never registered there.\n */\nexport interface ClaudeAgentCommandService {\n list(agent: unknown): readonly Pick<CommandDescriptor, 'name'>[]\n}\n\n/**\n * Cordis service name the preset route provides (behind an entry-local\n * isolate realm, so each session gets its own instance) and the host reads\n * back with dsh-agent-presets' official `serviceForAgent(ctx, agent, name)`\n * — the supported cross-scope read for callers that already hold the agent.\n */\nexport const CLAUDE_COMMANDS_SERVICE = 'claudeCommands'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n claudeCommands?: ClaudeAgentCommandService\n }\n}\n\nexport interface ClaudeCommandView {\n publicName: string\n claudeName: string\n description: string\n hint?: string\n prefixed: boolean\n}\n\nfunction bounded(value: string, maxChars: number): string {\n return redactText(value, maxChars)\n}\n\nexport function projectClaudeCommands(\n catalog: readonly SlashCommand[],\n target: ClaudeCommandTarget,\n): readonly ClaudeCommandView[] {\n const reservedNames = new Set(target.list().map(command => command.name))\n // Client contributions are absent from the Host directory but share the\n // same slash menu, so reserve their public names explicitly.\n for (const name of CLIENT_CONTRIBUTION_NAMES) reservedNames.add(name)\n const assigned = new Set<string>()\n const views: ClaudeCommandView[] = []\n for (const command of catalog) {\n const names = [command.name, ...(command.aliases ?? [])]\n const commandBases = new Set<string>()\n for (const claudeName of names) {\n const base = registryName(claudeName)\n if (base === undefined || commandBases.has(base)) continue\n commandBases.add(base)\n let publicName = base\n let prefixed = false\n if (reservedNames.has(publicName) || assigned.has(publicName)) {\n publicName = `claude-${base}`\n prefixed = true\n }\n if (!COMMAND_NAME.test(publicName) || reservedNames.has(publicName) || assigned.has(publicName)) continue\n const description = bounded(command.description || `Claude Code /${command.name}`, MAX_DESCRIPTION_CHARS)\n const hint = bounded(command.argumentHint ?? '', MAX_HINT_CHARS)\n views.push({\n publicName,\n claudeName,\n description,\n ...(hint.length === 0 ? {} : { hint }),\n prefixed,\n })\n assigned.add(publicName)\n }\n }\n return views\n}\n"],"mappings":";;AAIA,MAAM,eAAe;AACrB,MAAM,wBAAwB;AAC9B,MAAM,iBAAiB;;;;AAKvB,SAAS,aAAa,YAAwC;CAC5D,IAAI,aAAa,KAAK,UAAU,GAAG,OAAO;CAC1C,MAAM,QAAQ,WAAW,SAAS,GAAG,IAAI,WAAW,MAAM,WAAW,YAAY,GAAG,IAAI,CAAC,IAAI,KAAA;CAC7F,IAAI,UAAU,KAAA,KAAa,aAAa,KAAK,KAAK,GAAG,OAAO;CAC5D,MAAM,YAAY,WAAW,WAAW,KAAK,GAAG,CAAC,CAAC,YAAY;CAC9D,OAAO,aAAa,KAAK,SAAS,IAAI,YAAY,KAAA;AACpD;;;;;;;;;AAUA,MAAM,4CAAiD,IAAI,IAAI,CAAC,OAAO,CAAC;;;;;;;AAuBxE,MAAa,0BAA0B;AAgBvC,SAAS,QAAQ,OAAe,UAA0B;CACxD,OAAO,WAAW,OAAO,QAAQ;AACnC;AAEA,SAAgB,sBACd,SACA,QAC8B;CAC9B,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,CAAC,CAAC,KAAI,YAAW,QAAQ,IAAI,CAAC;CAGxE,KAAK,MAAM,QAAQ,2BAA2B,cAAc,IAAI,IAAI;CACpE,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,QAA6B,CAAC;CACpC,KAAK,MAAM,WAAW,SAAS;EAC7B,MAAM,QAAQ,CAAC,QAAQ,MAAM,GAAI,QAAQ,WAAW,CAAC,CAAE;EACvD,MAAM,+BAAe,IAAI,IAAY;EACrC,KAAK,MAAM,cAAc,OAAO;GAC9B,MAAM,OAAO,aAAa,UAAU;GACpC,IAAI,SAAS,KAAA,KAAa,aAAa,IAAI,IAAI,GAAG;GAClD,aAAa,IAAI,IAAI;GACrB,IAAI,aAAa;GACjB,IAAI,WAAW;GACf,IAAI,cAAc,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG;IAC7D,aAAa,UAAU;IACvB,WAAW;GACb;GACA,IAAI,CAAC,aAAa,KAAK,UAAU,KAAK,cAAc,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG;GACjG,MAAM,cAAc,QAAQ,QAAQ,eAAe,gBAAgB,QAAQ,QAAQ,qBAAqB;GACxG,MAAM,OAAO,QAAQ,QAAQ,gBAAgB,IAAI,cAAc;GAC/D,MAAM,KAAK;IACT;IACA;IACA;IACA,GAAI,KAAK,WAAW,IAAI,CAAC,IAAI,EAAE,KAAK;IACpC;GACF,CAAC;GACD,SAAS,IAAI,UAAU;EACzB;CACF;CACA,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-BdDs9ebF.mjs","names":[],"sources":["../src/constants.ts","../src/events.ts"],"sourcesContent":["export const CLAUDE_CODE_PROVIDER = 'claude'\r\nexport const CLAUDE_CODE_PRESET_ID = 'claude'\r\nexport const CLAUDE_CODE_PROVIDER_IDS = [CLAUDE_CODE_PROVIDER] as const\r\nexport const CLAUDE_SESSION_BOUND_EVENT = 'claude-code/session-bound'\r\nexport const CLAUDE_ACTIVITY_EVENT = 'claude-code/activity'\r\nexport const CLAUDE_CONTEXT_USAGE_EVENT = 'claude-code/context-usage'\r\nexport const CLAUDE_TASKS_EVENT = 'claude-code/tasks'\r\n/** Claude's subagent dispatch tools; rendered as plugin-owned group cards\r\n * gathering subagent activity instead of native tool cards. */\r\nexport const TASK_TOOL_NAMES: ReadonlySet<string> = new Set(['Task', 'Agent'])\r\nexport const SDK_VERSION = '0.3.233'\r\nexport const CLAUDE_DOCTOR_PATH = '/plugins/dsh-claude/doctor'\r\nexport const CLAUDE_UPDATE_CHECK_PATH = '/plugins/dsh-claude/update/check'\r\nexport const CLAUDE_UPDATE_PATH = '/plugins/dsh-claude/update'\r\nexport const CLAUDE_PROJECTION_PATH = '/plugins/dsh-claude/projection'\r\nexport const CLAUDE_GLOBAL_SETTINGS_PATH = '/plugins/dsh-claude/settings/global'\r\nexport const CLAUDE_REPOSITORY_SETUP_PATH = '/plugins/dsh-claude/repository/setup'\r\nexport const CLAUDE_REPOSITORY_ACTION_PATH = '/plugins/dsh-claude/repository/action'\r\nexport const CLAUDE_REVIEW_COMMENT_PATH = '/plugins/dsh-claude/review-comments'\r\n","import type { SessionEvent } from '@deepseek-ai/dsh-session'\r\nimport {\r\n CLAUDE_ACTIVITY_EVENT,\r\n CLAUDE_CONTEXT_USAGE_EVENT,\r\n CLAUDE_SESSION_BOUND_EVENT,\r\n CLAUDE_TASKS_EVENT,\r\n} from './constants.ts'\r\n\r\nexport type ClaudeActivityKind =\r\n | 'text'\r\n | 'status'\r\n | 'thinking'\r\n | 'tool-call'\r\n | 'tool-result'\r\n | 'permission'\r\n | 'question'\r\n | 'subagent'\r\n | 'usage'\r\n | 'warning'\r\n | 'error'\r\n\r\nexport type ClaudeActivityPhase =\r\n | 'started'\r\n | 'updated'\r\n | 'completed'\r\n | 'denied'\r\n | 'failed'\r\n\r\nexport interface ClaudeUsage {\r\n inputTokens?: number\r\n outputTokens?: number\r\n cacheReadTokens?: number\r\n cacheCreationTokens?: number\r\n cumulativeCostUsd?: number\r\n}\r\n\r\nexport interface ClaudeSessionBoundEvent {\r\n claudeSessionId: string\r\n cliVersion?: string\r\n sdkVersion: string\r\n cwd: string\r\n}\r\n\r\nexport interface ClaudeActivityEvent {\r\n turn: number\r\n step: number\r\n ordinal: number\r\n kind: ClaudeActivityKind\r\n phase?: ClaudeActivityPhase\r\n /** Claude task-board identity for lifecycle activity; never a transcript path. */\r\n taskId?: string\r\n toolUseId?: string\r\n /** Enclosing Claude tool call for subagent-nested activity. */\r\n parentToolUseId?: string\r\n toolName?: string\r\n title?: string\r\n summary?: string\r\n detail?: string\r\n /** Redacted visible Claude prose used by the plugin-owned interleaved transcript. */\r\n text?: string\r\n isError?: boolean\r\n usage?: ClaudeUsage\r\n}\r\n\r\nexport interface ClaudeContextUsageCategory {\r\n name: string\r\n tokens: number\r\n color: string\r\n isDeferred?: boolean\r\n}\r\n\r\nexport interface ClaudeContextUsageEvent {\r\n model: string\r\n totalTokens: number\r\n maxTokens: number\r\n percentage: number\r\n categories: readonly ClaudeContextUsageCategory[]\r\n}\r\n\r\nexport interface ClaudeContextUsageInput {\r\n model: unknown\r\n totalTokens: unknown\r\n maxTokens: unknown\r\n percentage: unknown\r\n categories: readonly {\r\n name?: unknown\r\n tokens?: unknown\r\n color?: unknown\r\n isDeferred?: unknown\r\n }[]\r\n}\r\n\r\ndeclare module '@deepseek-ai/dsh-session/types' {\r\n interface SessionEventMap {\r\n 'claude-code/session-bound': ClaudeSessionBoundEvent\r\n 'claude-code/activity': ClaudeActivityEvent\r\n 'claude-code/context-usage': ClaudeContextUsageEvent\r\n 'claude-code/tasks': ClaudeTasksEvent\r\n }\r\n}\r\n\r\nconst SECRET_KEY = /(?:^|[_-])(password|passwd|secret|token|api[_-]?key|authorization|credential|private[_-]?key|session[_-]?key|env|environ|environment)(?:$|[_-])/i\r\nconst MAX_SUMMARY_CHARS = 1_000\r\nconst MAX_DETAIL_CHARS = 4_000\r\nconst MAX_TRANSCRIPT_TEXT_CHARS = 64_000\r\nconst MAX_DEPTH = 6\r\nconst MAX_ARRAY_ITEMS = 40\r\nconst MAX_OBJECT_KEYS = 60\r\nconst REDACTED = '[REDACTED]'\r\nconst TRUNCATED = '…[truncated]'\r\nconst SECRET_ASSIGNMENT = /((?:password|passwd|secret|token|api[_-]?key|authorization|credential|private[_-]?key|session[_-]?key)\\s*(?:=|:)\\s*)(?:\"[^\"]*\"|'[^']*'|[^\\s,;&]+)/giu\r\nconst BEARER_TOKEN = /(\\bbearer\\s+)[A-Za-z0-9._~+/=-]+/giu\r\nconst PREFIXED_TOKEN = /\\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|ghp_|github_pat_)[A-Za-z0-9_-]{8,}/giu\r\nconst JWT_TOKEN = /\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu\r\nconst URL_USERINFO = /([a-z][a-z0-9+.-]*:\\/\\/[^:\\s/@]+:)[^@\\s/]+@/giu\r\nconst URL_SECRET_PARAM = /([?&](?:password|secret|token|api[_-]?key|access[_-]?token|refresh[_-]?token)=)[^&#\\s]+/giu\r\n\r\nexport function boundText(value: string, maxChars: number): string {\r\n if (value.length <= maxChars) return value\r\n return `${value.slice(0, Math.max(0, maxChars - TRUNCATED.length))}${TRUNCATED}`\r\n}\r\n\r\nexport function redactText(value: string, maxChars = MAX_DETAIL_CHARS): string {\r\n return boundText(\r\n value\r\n .replace(JWT_TOKEN, REDACTED)\r\n .replace(PREFIXED_TOKEN, REDACTED)\r\n .replace(BEARER_TOKEN, `$1${REDACTED}`)\r\n .replace(URL_USERINFO, `$1${REDACTED}@`)\r\n .replace(URL_SECRET_PARAM, `$1${REDACTED}`)\r\n .replace(SECRET_ASSIGNMENT, `$1${REDACTED}`),\r\n maxChars,\r\n )\r\n}\r\n\r\nexport function redactValue(value: unknown, depth = 0, seen = new WeakSet<object>()): unknown {\r\n if (depth > MAX_DEPTH) return '[max-depth]'\r\n if (value === null || typeof value === 'boolean' || typeof value === 'number') return value\r\n if (typeof value === 'string') return redactText(value)\r\n if (typeof value === 'bigint') return value.toString()\r\n if (typeof value === 'undefined') return null\r\n if (typeof value === 'function' || typeof value === 'symbol') return `[${typeof value}]`\r\n if (value instanceof Error) {\r\n return { name: value.name, message: redactText(value.message, MAX_SUMMARY_CHARS) }\r\n }\r\n if (typeof value !== 'object') return String(value)\r\n if (seen.has(value)) return '[circular]'\r\n seen.add(value)\r\n try {\r\n if (Array.isArray(value)) {\r\n const items = value.slice(0, MAX_ARRAY_ITEMS).map(item => redactValue(item, depth + 1, seen))\r\n if (value.length > MAX_ARRAY_ITEMS) items.push(`[${value.length - MAX_ARRAY_ITEMS} more items]`)\r\n return items\r\n }\r\n const result: Record<string, unknown> = {}\r\n const entries = Object.entries(value as Record<string, unknown>)\r\n for (const [key, item] of entries.slice(0, MAX_OBJECT_KEYS)) {\r\n result[key] = SECRET_KEY.test(key) ? REDACTED : redactValue(item, depth + 1, seen)\r\n }\r\n if (entries.length > MAX_OBJECT_KEYS) result.__truncatedKeys = entries.length - MAX_OBJECT_KEYS\r\n return result\r\n } finally {\r\n seen.delete(value)\r\n }\r\n}\r\n\r\nexport function safeDetail(value: unknown): string | undefined {\r\n if (value === undefined) return undefined\r\n const redacted = redactValue(value)\r\n const text = typeof redacted === 'string' ? redacted : JSON.stringify(redacted)\r\n return boundText(text, MAX_DETAIL_CHARS)\r\n}\r\n\r\nexport function normalizeActivity(\r\n activity: Omit<ClaudeActivityEvent, 'summary' | 'detail'> & {\r\n summary?: unknown\r\n detail?: unknown\r\n },\r\n): ClaudeActivityEvent {\r\n const normalized: ClaudeActivityEvent = {\r\n turn: activity.turn,\r\n step: activity.step,\r\n ordinal: activity.ordinal,\r\n kind: activity.kind,\r\n }\r\n if (activity.phase !== undefined) normalized.phase = activity.phase\r\n if (activity.taskId !== undefined) normalized.taskId = redactText(activity.taskId, 128)\r\n if (activity.toolUseId !== undefined) normalized.toolUseId = redactText(activity.toolUseId, 256)\r\n if (activity.parentToolUseId !== undefined) normalized.parentToolUseId = redactText(activity.parentToolUseId, 256)\r\n if (activity.toolName !== undefined) normalized.toolName = redactText(activity.toolName, 256)\r\n if (activity.title !== undefined) normalized.title = redactText(activity.title, MAX_SUMMARY_CHARS)\r\n if (activity.summary !== undefined) {\r\n normalized.summary = redactText(\r\n typeof activity.summary === 'string' ? activity.summary : safeDetail(activity.summary) ?? '',\r\n MAX_SUMMARY_CHARS,\r\n )\r\n }\r\n const detail = safeDetail(activity.detail)\r\n if (detail !== undefined) normalized.detail = detail\r\n if (activity.text !== undefined) normalized.text = redactText(activity.text, MAX_TRANSCRIPT_TEXT_CHARS)\r\n if (activity.isError !== undefined) normalized.isError = activity.isError\r\n if (activity.usage !== undefined) normalized.usage = { ...activity.usage }\r\n return normalized\r\n}\r\n\r\nconst MAX_CONTEXT_CATEGORIES = 24\r\nconst FALLBACK_CONTEXT_COLOR = '#8b95a5'\r\nconst SAFE_CONTEXT_COLOR = /^#[0-9a-f]{3,8}$/iu\r\n\r\nfunction nonNegativeInteger(value: unknown): number {\r\n return typeof value === 'number' && Number.isFinite(value)\r\n ? Math.max(0, Math.floor(value))\r\n : 0\r\n}\r\n\r\nexport function normalizeContextUsage(input: ClaudeContextUsageInput): ClaudeContextUsageEvent {\r\n return {\r\n model: redactText(typeof input.model === 'string' ? input.model : 'unknown', 128),\r\n totalTokens: nonNegativeInteger(input.totalTokens),\r\n maxTokens: nonNegativeInteger(input.maxTokens),\r\n percentage: Math.min(100, nonNegativeInteger(input.percentage)),\r\n categories: input.categories.slice(0, MAX_CONTEXT_CATEGORIES).map(category => ({\r\n name: redactText(typeof category.name === 'string' ? category.name : 'Unknown', 128),\r\n tokens: nonNegativeInteger(category.tokens),\r\n color: typeof category.color === 'string' && SAFE_CONTEXT_COLOR.test(category.color)\r\n ? category.color\r\n : FALLBACK_CONTEXT_COLOR,\r\n ...(category.isDeferred === true ? { isDeferred: true } : {}),\r\n })),\r\n }\r\n}\r\n\r\nexport function latestClaudeContextUsage(\r\n events: readonly SessionEvent[],\r\n): ClaudeContextUsageEvent | undefined {\r\n for (let index = events.length - 1; index >= 0; index -= 1) {\r\n const event = events[index]\r\n if (event?.type === CLAUDE_CONTEXT_USAGE_EVENT) return event.data as ClaudeContextUsageEvent\r\n }\r\n return undefined\r\n}\r\n\r\nexport type ClaudeTaskStatus = 'running' | 'completed' | 'failed' | 'stopped' | 'killed'\r\n\r\nexport interface ClaudeTaskUsage {\r\n totalTokens?: number\r\n toolUses?: number\r\n durationMs?: number\r\n}\r\n\r\nexport interface ClaudeTaskInfo {\r\n taskId: string\r\n description: string\r\n status: ClaudeTaskStatus\r\n /** DSH turn during which this task was first observed, when known. */\r\n originTurn?: number\r\n subagentType?: string\r\n taskType?: string\r\n lastToolName?: string\r\n summary?: string\r\n usage?: ClaudeTaskUsage\r\n /** True while the task runs detached (background command/subagent). */\r\n backgrounded?: boolean\r\n}\r\n\r\n/** Level snapshot of one session's Claude task board, REPLACE semantics. */\r\nexport interface ClaudeTasksEvent {\r\n tasks: readonly ClaudeTaskInfo[]\r\n}\r\n\r\nconst MAX_TASKS_PER_SNAPSHOT = 50\r\nconst MAX_TASK_TEXT_CHARS = 300\r\n\r\nconst TASK_STATUSES: ReadonlySet<string> = new Set(['running', 'completed', 'failed', 'stopped', 'killed'])\r\n\r\nfunction normalizeTaskUsage(input: ClaudeTaskUsage | undefined): ClaudeTaskUsage | undefined {\r\n if (input === undefined) return undefined\r\n const usage: ClaudeTaskUsage = {}\r\n if (input.totalTokens !== undefined) usage.totalTokens = nonNegativeInteger(input.totalTokens)\r\n if (input.toolUses !== undefined) usage.toolUses = nonNegativeInteger(input.toolUses)\r\n if (input.durationMs !== undefined) usage.durationMs = nonNegativeInteger(input.durationMs)\r\n return Object.keys(usage).length === 0 ? undefined : usage\r\n}\r\n\r\nexport function normalizeTasksEvent(tasks: readonly ClaudeTaskInfo[]): ClaudeTasksEvent {\r\n return {\r\n tasks: tasks.slice(0, MAX_TASKS_PER_SNAPSHOT).map(task => {\r\n const usage = normalizeTaskUsage(task.usage)\r\n return {\r\n taskId: redactText(String(task.taskId), 128),\r\n description: redactText(String(task.description), MAX_TASK_TEXT_CHARS),\r\n status: TASK_STATUSES.has(task.status) ? task.status : 'running',\r\n ...(task.originTurn === undefined ? {} : { originTurn: nonNegativeInteger(task.originTurn) }),\r\n ...(task.subagentType === undefined ? {} : { subagentType: redactText(task.subagentType, 64) }),\r\n ...(task.taskType === undefined ? {} : { taskType: redactText(task.taskType, 64) }),\r\n ...(task.lastToolName === undefined ? {} : { lastToolName: redactText(task.lastToolName, 64) }),\r\n ...(task.summary === undefined ? {} : { summary: redactText(task.summary, MAX_TASK_TEXT_CHARS) }),\r\n ...(usage === undefined ? {} : { usage }),\r\n ...(task.backgrounded === true ? { backgrounded: true } : {}),\r\n }\r\n }),\r\n }\r\n}\r\n\r\nexport function latestClaudeTasks(\r\n events: readonly SessionEvent[],\r\n): ClaudeTasksEvent | undefined {\r\n for (let index = events.length - 1; index >= 0; index -= 1) {\r\n const event = events[index]\r\n if (event?.type === CLAUDE_TASKS_EVENT) return event.data as ClaudeTasksEvent\r\n }\r\n return undefined\r\n}\r\n\r\nexport type ClaudeActivityInput = Omit<\r\n ClaudeActivityEvent,\r\n 'turn' | 'step' | 'ordinal' | 'summary' | 'detail'\r\n> & {\r\n summary?: unknown\r\n detail?: unknown\r\n}\r\n\r\nexport interface ClaudeActivityCursor {\r\n turn: number\r\n step: number\r\n nextOrdinal: number\r\n}\r\n\r\n/** Derive the current DSH turn/step; activity ordinals are completed from the sidecar. */\r\nexport function currentClaudeActivityCursor(events: readonly SessionEvent[]): ClaudeActivityCursor {\r\n let turn = 0\r\n let step = 0\r\n for (const event of events) {\r\n if (event.type !== 'step/start') continue\r\n const data = event.data as { turn: number; step: number }\r\n turn = data.turn\r\n step = data.step\r\n }\r\n if (turn < 1 || step < 1) {\r\n throw new Error('dsh-claude: Claude activity requires an open DSH step')\r\n }\r\n return { turn, step, nextOrdinal: 0 }\r\n}\r\n\r\nexport function latestClaudeSessionBinding(\r\n events: readonly SessionEvent[],\r\n): ClaudeSessionBoundEvent | undefined {\r\n for (let index = events.length - 1; index >= 0; index -= 1) {\r\n const event = events[index]\r\n if (event?.type === CLAUDE_SESSION_BOUND_EVENT) {\r\n return event.data as ClaudeSessionBoundEvent\r\n }\r\n }\r\n return undefined\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"events-BdDs9ebF.mjs","names":[],"sources":["../src/constants.ts","../src/events.ts"],"sourcesContent":["export const CLAUDE_CODE_PROVIDER = 'claude'\nexport const CLAUDE_CODE_PRESET_ID = 'claude'\nexport const CLAUDE_CODE_PROVIDER_IDS = [CLAUDE_CODE_PROVIDER] as const\nexport const CLAUDE_SESSION_BOUND_EVENT = 'claude-code/session-bound'\nexport const CLAUDE_ACTIVITY_EVENT = 'claude-code/activity'\nexport const CLAUDE_CONTEXT_USAGE_EVENT = 'claude-code/context-usage'\nexport const CLAUDE_TASKS_EVENT = 'claude-code/tasks'\n/** Claude's subagent dispatch tools; rendered as plugin-owned group cards\n * gathering subagent activity instead of native tool cards. */\nexport const TASK_TOOL_NAMES: ReadonlySet<string> = new Set(['Task', 'Agent'])\nexport const SDK_VERSION = '0.3.233'\nexport const CLAUDE_DOCTOR_PATH = '/plugins/dsh-claude/doctor'\nexport const CLAUDE_UPDATE_CHECK_PATH = '/plugins/dsh-claude/update/check'\nexport const CLAUDE_UPDATE_PATH = '/plugins/dsh-claude/update'\nexport const CLAUDE_PROJECTION_PATH = '/plugins/dsh-claude/projection'\nexport const CLAUDE_GLOBAL_SETTINGS_PATH = '/plugins/dsh-claude/settings/global'\nexport const CLAUDE_REPOSITORY_SETUP_PATH = '/plugins/dsh-claude/repository/setup'\nexport const CLAUDE_REPOSITORY_ACTION_PATH = '/plugins/dsh-claude/repository/action'\nexport const CLAUDE_REVIEW_COMMENT_PATH = '/plugins/dsh-claude/review-comments'\n","import type { SessionEvent } from '@deepseek-ai/dsh-session'\nimport {\n CLAUDE_ACTIVITY_EVENT,\n CLAUDE_CONTEXT_USAGE_EVENT,\n CLAUDE_SESSION_BOUND_EVENT,\n CLAUDE_TASKS_EVENT,\n} from './constants.ts'\n\nexport type ClaudeActivityKind =\n | 'text'\n | 'status'\n | 'thinking'\n | 'tool-call'\n | 'tool-result'\n | 'permission'\n | 'question'\n | 'subagent'\n | 'usage'\n | 'warning'\n | 'error'\n\nexport type ClaudeActivityPhase =\n | 'started'\n | 'updated'\n | 'completed'\n | 'denied'\n | 'failed'\n\nexport interface ClaudeUsage {\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheCreationTokens?: number\n cumulativeCostUsd?: number\n}\n\nexport interface ClaudeSessionBoundEvent {\n claudeSessionId: string\n cliVersion?: string\n sdkVersion: string\n cwd: string\n}\n\nexport interface ClaudeActivityEvent {\n turn: number\n step: number\n ordinal: number\n kind: ClaudeActivityKind\n phase?: ClaudeActivityPhase\n /** Claude task-board identity for lifecycle activity; never a transcript path. */\n taskId?: string\n toolUseId?: string\n /** Enclosing Claude tool call for subagent-nested activity. */\n parentToolUseId?: string\n toolName?: string\n title?: string\n summary?: string\n detail?: string\n /** Redacted visible Claude prose used by the plugin-owned interleaved transcript. */\n text?: string\n isError?: boolean\n usage?: ClaudeUsage\n}\n\nexport interface ClaudeContextUsageCategory {\n name: string\n tokens: number\n color: string\n isDeferred?: boolean\n}\n\nexport interface ClaudeContextUsageEvent {\n model: string\n totalTokens: number\n maxTokens: number\n percentage: number\n categories: readonly ClaudeContextUsageCategory[]\n}\n\nexport interface ClaudeContextUsageInput {\n model: unknown\n totalTokens: unknown\n maxTokens: unknown\n percentage: unknown\n categories: readonly {\n name?: unknown\n tokens?: unknown\n color?: unknown\n isDeferred?: unknown\n }[]\n}\n\ndeclare module '@deepseek-ai/dsh-session/types' {\n interface SessionEventMap {\n 'claude-code/session-bound': ClaudeSessionBoundEvent\n 'claude-code/activity': ClaudeActivityEvent\n 'claude-code/context-usage': ClaudeContextUsageEvent\n 'claude-code/tasks': ClaudeTasksEvent\n }\n}\n\nconst SECRET_KEY = /(?:^|[_-])(password|passwd|secret|token|api[_-]?key|authorization|credential|private[_-]?key|session[_-]?key|env|environ|environment)(?:$|[_-])/i\nconst MAX_SUMMARY_CHARS = 1_000\nconst MAX_DETAIL_CHARS = 4_000\nconst MAX_TRANSCRIPT_TEXT_CHARS = 64_000\nconst MAX_DEPTH = 6\nconst MAX_ARRAY_ITEMS = 40\nconst MAX_OBJECT_KEYS = 60\nconst REDACTED = '[REDACTED]'\nconst TRUNCATED = '…[truncated]'\nconst SECRET_ASSIGNMENT = /((?:password|passwd|secret|token|api[_-]?key|authorization|credential|private[_-]?key|session[_-]?key)\\s*(?:=|:)\\s*)(?:\"[^\"]*\"|'[^']*'|[^\\s,;&]+)/giu\nconst BEARER_TOKEN = /(\\bbearer\\s+)[A-Za-z0-9._~+/=-]+/giu\nconst PREFIXED_TOKEN = /\\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|ghp_|github_pat_)[A-Za-z0-9_-]{8,}/giu\nconst JWT_TOKEN = /\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/gu\nconst URL_USERINFO = /([a-z][a-z0-9+.-]*:\\/\\/[^:\\s/@]+:)[^@\\s/]+@/giu\nconst URL_SECRET_PARAM = /([?&](?:password|secret|token|api[_-]?key|access[_-]?token|refresh[_-]?token)=)[^&#\\s]+/giu\n\nexport function boundText(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value\n return `${value.slice(0, Math.max(0, maxChars - TRUNCATED.length))}${TRUNCATED}`\n}\n\nexport function redactText(value: string, maxChars = MAX_DETAIL_CHARS): string {\n return boundText(\n value\n .replace(JWT_TOKEN, REDACTED)\n .replace(PREFIXED_TOKEN, REDACTED)\n .replace(BEARER_TOKEN, `$1${REDACTED}`)\n .replace(URL_USERINFO, `$1${REDACTED}@`)\n .replace(URL_SECRET_PARAM, `$1${REDACTED}`)\n .replace(SECRET_ASSIGNMENT, `$1${REDACTED}`),\n maxChars,\n )\n}\n\nexport function redactValue(value: unknown, depth = 0, seen = new WeakSet<object>()): unknown {\n if (depth > MAX_DEPTH) return '[max-depth]'\n if (value === null || typeof value === 'boolean' || typeof value === 'number') return value\n if (typeof value === 'string') return redactText(value)\n if (typeof value === 'bigint') return value.toString()\n if (typeof value === 'undefined') return null\n if (typeof value === 'function' || typeof value === 'symbol') return `[${typeof value}]`\n if (value instanceof Error) {\n return { name: value.name, message: redactText(value.message, MAX_SUMMARY_CHARS) }\n }\n if (typeof value !== 'object') return String(value)\n if (seen.has(value)) return '[circular]'\n seen.add(value)\n try {\n if (Array.isArray(value)) {\n const items = value.slice(0, MAX_ARRAY_ITEMS).map(item => redactValue(item, depth + 1, seen))\n if (value.length > MAX_ARRAY_ITEMS) items.push(`[${value.length - MAX_ARRAY_ITEMS} more items]`)\n return items\n }\n const result: Record<string, unknown> = {}\n const entries = Object.entries(value as Record<string, unknown>)\n for (const [key, item] of entries.slice(0, MAX_OBJECT_KEYS)) {\n result[key] = SECRET_KEY.test(key) ? REDACTED : redactValue(item, depth + 1, seen)\n }\n if (entries.length > MAX_OBJECT_KEYS) result.__truncatedKeys = entries.length - MAX_OBJECT_KEYS\n return result\n } finally {\n seen.delete(value)\n }\n}\n\nexport function safeDetail(value: unknown): string | undefined {\n if (value === undefined) return undefined\n const redacted = redactValue(value)\n const text = typeof redacted === 'string' ? redacted : JSON.stringify(redacted)\n return boundText(text, MAX_DETAIL_CHARS)\n}\n\nexport function normalizeActivity(\n activity: Omit<ClaudeActivityEvent, 'summary' | 'detail'> & {\n summary?: unknown\n detail?: unknown\n },\n): ClaudeActivityEvent {\n const normalized: ClaudeActivityEvent = {\n turn: activity.turn,\n step: activity.step,\n ordinal: activity.ordinal,\n kind: activity.kind,\n }\n if (activity.phase !== undefined) normalized.phase = activity.phase\n if (activity.taskId !== undefined) normalized.taskId = redactText(activity.taskId, 128)\n if (activity.toolUseId !== undefined) normalized.toolUseId = redactText(activity.toolUseId, 256)\n if (activity.parentToolUseId !== undefined) normalized.parentToolUseId = redactText(activity.parentToolUseId, 256)\n if (activity.toolName !== undefined) normalized.toolName = redactText(activity.toolName, 256)\n if (activity.title !== undefined) normalized.title = redactText(activity.title, MAX_SUMMARY_CHARS)\n if (activity.summary !== undefined) {\n normalized.summary = redactText(\n typeof activity.summary === 'string' ? activity.summary : safeDetail(activity.summary) ?? '',\n MAX_SUMMARY_CHARS,\n )\n }\n const detail = safeDetail(activity.detail)\n if (detail !== undefined) normalized.detail = detail\n if (activity.text !== undefined) normalized.text = redactText(activity.text, MAX_TRANSCRIPT_TEXT_CHARS)\n if (activity.isError !== undefined) normalized.isError = activity.isError\n if (activity.usage !== undefined) normalized.usage = { ...activity.usage }\n return normalized\n}\n\nconst MAX_CONTEXT_CATEGORIES = 24\nconst FALLBACK_CONTEXT_COLOR = '#8b95a5'\nconst SAFE_CONTEXT_COLOR = /^#[0-9a-f]{3,8}$/iu\n\nfunction nonNegativeInteger(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value)\n ? Math.max(0, Math.floor(value))\n : 0\n}\n\nexport function normalizeContextUsage(input: ClaudeContextUsageInput): ClaudeContextUsageEvent {\n return {\n model: redactText(typeof input.model === 'string' ? input.model : 'unknown', 128),\n totalTokens: nonNegativeInteger(input.totalTokens),\n maxTokens: nonNegativeInteger(input.maxTokens),\n percentage: Math.min(100, nonNegativeInteger(input.percentage)),\n categories: input.categories.slice(0, MAX_CONTEXT_CATEGORIES).map(category => ({\n name: redactText(typeof category.name === 'string' ? category.name : 'Unknown', 128),\n tokens: nonNegativeInteger(category.tokens),\n color: typeof category.color === 'string' && SAFE_CONTEXT_COLOR.test(category.color)\n ? category.color\n : FALLBACK_CONTEXT_COLOR,\n ...(category.isDeferred === true ? { isDeferred: true } : {}),\n })),\n }\n}\n\nexport function latestClaudeContextUsage(\n events: readonly SessionEvent[],\n): ClaudeContextUsageEvent | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index]\n if (event?.type === CLAUDE_CONTEXT_USAGE_EVENT) return event.data as ClaudeContextUsageEvent\n }\n return undefined\n}\n\nexport type ClaudeTaskStatus = 'running' | 'completed' | 'failed' | 'stopped' | 'killed'\n\nexport interface ClaudeTaskUsage {\n totalTokens?: number\n toolUses?: number\n durationMs?: number\n}\n\nexport interface ClaudeTaskInfo {\n taskId: string\n description: string\n status: ClaudeTaskStatus\n /** DSH turn during which this task was first observed, when known. */\n originTurn?: number\n subagentType?: string\n taskType?: string\n lastToolName?: string\n summary?: string\n usage?: ClaudeTaskUsage\n /** True while the task runs detached (background command/subagent). */\n backgrounded?: boolean\n}\n\n/** Level snapshot of one session's Claude task board, REPLACE semantics. */\nexport interface ClaudeTasksEvent {\n tasks: readonly ClaudeTaskInfo[]\n}\n\nconst MAX_TASKS_PER_SNAPSHOT = 50\nconst MAX_TASK_TEXT_CHARS = 300\n\nconst TASK_STATUSES: ReadonlySet<string> = new Set(['running', 'completed', 'failed', 'stopped', 'killed'])\n\nfunction normalizeTaskUsage(input: ClaudeTaskUsage | undefined): ClaudeTaskUsage | undefined {\n if (input === undefined) return undefined\n const usage: ClaudeTaskUsage = {}\n if (input.totalTokens !== undefined) usage.totalTokens = nonNegativeInteger(input.totalTokens)\n if (input.toolUses !== undefined) usage.toolUses = nonNegativeInteger(input.toolUses)\n if (input.durationMs !== undefined) usage.durationMs = nonNegativeInteger(input.durationMs)\n return Object.keys(usage).length === 0 ? undefined : usage\n}\n\nexport function normalizeTasksEvent(tasks: readonly ClaudeTaskInfo[]): ClaudeTasksEvent {\n return {\n tasks: tasks.slice(0, MAX_TASKS_PER_SNAPSHOT).map(task => {\n const usage = normalizeTaskUsage(task.usage)\n return {\n taskId: redactText(String(task.taskId), 128),\n description: redactText(String(task.description), MAX_TASK_TEXT_CHARS),\n status: TASK_STATUSES.has(task.status) ? task.status : 'running',\n ...(task.originTurn === undefined ? {} : { originTurn: nonNegativeInteger(task.originTurn) }),\n ...(task.subagentType === undefined ? {} : { subagentType: redactText(task.subagentType, 64) }),\n ...(task.taskType === undefined ? {} : { taskType: redactText(task.taskType, 64) }),\n ...(task.lastToolName === undefined ? {} : { lastToolName: redactText(task.lastToolName, 64) }),\n ...(task.summary === undefined ? {} : { summary: redactText(task.summary, MAX_TASK_TEXT_CHARS) }),\n ...(usage === undefined ? {} : { usage }),\n ...(task.backgrounded === true ? { backgrounded: true } : {}),\n }\n }),\n }\n}\n\nexport function latestClaudeTasks(\n events: readonly SessionEvent[],\n): ClaudeTasksEvent | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index]\n if (event?.type === CLAUDE_TASKS_EVENT) return event.data as ClaudeTasksEvent\n }\n return undefined\n}\n\nexport type ClaudeActivityInput = Omit<\n ClaudeActivityEvent,\n 'turn' | 'step' | 'ordinal' | 'summary' | 'detail'\n> & {\n summary?: unknown\n detail?: unknown\n}\n\nexport interface ClaudeActivityCursor {\n turn: number\n step: number\n nextOrdinal: number\n}\n\n/** Derive the current DSH turn/step; activity ordinals are completed from the sidecar. */\nexport function currentClaudeActivityCursor(events: readonly SessionEvent[]): ClaudeActivityCursor {\n let turn = 0\n let step = 0\n for (const event of events) {\n if (event.type !== 'step/start') continue\n const data = event.data as { turn: number; step: number }\n turn = data.turn\n step = data.step\n }\n if (turn < 1 || step < 1) {\n throw new Error('dsh-claude: Claude activity requires an open DSH step')\n }\n return { turn, step, nextOrdinal: 0 }\n}\n\nexport function latestClaudeSessionBinding(\n events: readonly SessionEvent[],\n): ClaudeSessionBoundEvent | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index]\n if (event?.type === CLAUDE_SESSION_BOUND_EVENT) {\n return event.data as ClaudeSessionBoundEvent\n }\n }\n return undefined\n}\n"],"mappings":"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"}
|
package/lib/index.d.mts
CHANGED
|
@@ -139,6 +139,26 @@ interface ClaudeSidecarProjection {
|
|
|
139
139
|
readonly contextUsage?: ClaudeContextUsageEvent;
|
|
140
140
|
readonly tasks?: ClaudeTasksEvent;
|
|
141
141
|
}
|
|
142
|
+
/** Change notification published to live subscribers after each accepted write. */
|
|
143
|
+
type ClaudeSidecarDelta = {
|
|
144
|
+
kind: 'text';
|
|
145
|
+
turn: number;
|
|
146
|
+
step: number;
|
|
147
|
+
ordinal: number;
|
|
148
|
+
append?: string;
|
|
149
|
+
text?: string;
|
|
150
|
+
} | {
|
|
151
|
+
kind: 'activity';
|
|
152
|
+
activity: ClaudeActivityEvent;
|
|
153
|
+
} | {
|
|
154
|
+
kind: 'contextUsage';
|
|
155
|
+
value: ClaudeContextUsageEvent;
|
|
156
|
+
} | {
|
|
157
|
+
kind: 'tasks';
|
|
158
|
+
value: ClaudeTasksEvent;
|
|
159
|
+
} | {
|
|
160
|
+
kind: 'sync';
|
|
161
|
+
};
|
|
142
162
|
interface ClaudeSidecarRepositoryOptions {
|
|
143
163
|
readonly root?: string;
|
|
144
164
|
readonly legacyRoot?: string;
|
|
@@ -149,6 +169,19 @@ declare class ClaudeSidecarRepository {
|
|
|
149
169
|
readonly legacyRoot: string | undefined;
|
|
150
170
|
constructor(options?: ClaudeSidecarRepositoryOptions);
|
|
151
171
|
read(sessionId: string): Promise<ClaudeSidecarProjection>;
|
|
172
|
+
/** Observe accepted changes for one session; returns the unsubscriber. */
|
|
173
|
+
subscribe(sessionId: string, listener: (delta: ClaudeSidecarDelta) => void): () => void;
|
|
174
|
+
/** Record streaming assistant prose without touching the disk on the hot
|
|
175
|
+
* path: subscribers are notified synchronously (as an append when the
|
|
176
|
+
* redacted text grows in place) and persistence is coalesced. */
|
|
177
|
+
appendTranscriptText(sessionId: string, value: {
|
|
178
|
+
turn: number;
|
|
179
|
+
step: number;
|
|
180
|
+
ordinal: number;
|
|
181
|
+
text: string;
|
|
182
|
+
}): void;
|
|
183
|
+
/** Persist any pending streaming transcript now (segment close, turn end). */
|
|
184
|
+
flushTranscriptText(sessionId: string): Promise<void>;
|
|
152
185
|
writeBinding(sessionId: string, value: Omit<ClaudeSessionBoundEvent, 'sdkVersion'> & {
|
|
153
186
|
sdkVersion?: string;
|
|
154
187
|
}): Promise<ClaudeSidecarProjection>;
|
package/lib/index.mjs
CHANGED
|
@@ -15,6 +15,9 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
//#region src/sidecar.ts
|
|
16
16
|
const SIDECAR_SCHEMA_VERSION = 1;
|
|
17
17
|
const MAX_ACTIVITIES = 1e4;
|
|
18
|
+
/** Trailing window that coalesces per-token transcript persistence into one
|
|
19
|
+
* atomic disk write; live subscribers are notified synchronously regardless. */
|
|
20
|
+
const TEXT_FLUSH_MS = 150;
|
|
18
21
|
function emptyProjection() {
|
|
19
22
|
return {
|
|
20
23
|
schemaVersion: SIDECAR_SCHEMA_VERSION,
|
|
@@ -117,13 +120,126 @@ var ClaudeSidecarRepository = class {
|
|
|
117
120
|
root;
|
|
118
121
|
legacyRoot;
|
|
119
122
|
#pending = /* @__PURE__ */ new Map();
|
|
123
|
+
/** Latest durable projection per session; disk is read once and written through. */
|
|
124
|
+
#latest = /* @__PURE__ */ new Map();
|
|
125
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
126
|
+
/** Streaming transcript segments not yet persisted, keyed by activity key. */
|
|
127
|
+
#live = /* @__PURE__ */ new Map();
|
|
128
|
+
/** Monotonic revision boost so merged reads advance while text stays in memory. */
|
|
129
|
+
#boost = /* @__PURE__ */ new Map();
|
|
130
|
+
#flushTimers = /* @__PURE__ */ new Map();
|
|
120
131
|
constructor(options = {}) {
|
|
121
132
|
this.root = options.root ?? dshHomePath("plugins", "dsh-claude", "sessions");
|
|
122
133
|
this.legacyRoot = options.legacyRoot ?? (options.root === void 0 ? dshHomePath("plugins", "dsh-claude-code", "sessions") : void 0);
|
|
123
134
|
}
|
|
124
135
|
async read(sessionId) {
|
|
125
136
|
await this.#pending.get(sessionId)?.catch(() => void 0);
|
|
126
|
-
return this.#
|
|
137
|
+
return this.#merged(sessionId, await this.#base(sessionId));
|
|
138
|
+
}
|
|
139
|
+
/** Observe accepted changes for one session; returns the unsubscriber. */
|
|
140
|
+
subscribe(sessionId, listener) {
|
|
141
|
+
let set = this.#listeners.get(sessionId);
|
|
142
|
+
if (set === void 0) {
|
|
143
|
+
set = /* @__PURE__ */ new Set();
|
|
144
|
+
this.#listeners.set(sessionId, set);
|
|
145
|
+
}
|
|
146
|
+
set.add(listener);
|
|
147
|
+
return () => {
|
|
148
|
+
set.delete(listener);
|
|
149
|
+
if (set.size === 0) this.#listeners.delete(sessionId);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** Record streaming assistant prose without touching the disk on the hot
|
|
153
|
+
* path: subscribers are notified synchronously (as an append when the
|
|
154
|
+
* redacted text grows in place) and persistence is coalesced. */
|
|
155
|
+
appendTranscriptText(sessionId, value) {
|
|
156
|
+
const normalized = normalizeActivity({
|
|
157
|
+
kind: "text",
|
|
158
|
+
phase: "updated",
|
|
159
|
+
...value
|
|
160
|
+
});
|
|
161
|
+
const key = activityKey(normalized);
|
|
162
|
+
let overlay = this.#live.get(sessionId);
|
|
163
|
+
if (overlay === void 0) {
|
|
164
|
+
overlay = /* @__PURE__ */ new Map();
|
|
165
|
+
this.#live.set(sessionId, overlay);
|
|
166
|
+
}
|
|
167
|
+
const previous = overlay.get(key);
|
|
168
|
+
overlay.set(key, normalized);
|
|
169
|
+
this.#boost.set(sessionId, (this.#boost.get(sessionId) ?? 0) + 1);
|
|
170
|
+
const text = normalized.text ?? "";
|
|
171
|
+
const base = {
|
|
172
|
+
turn: normalized.turn,
|
|
173
|
+
step: normalized.step,
|
|
174
|
+
ordinal: normalized.ordinal
|
|
175
|
+
};
|
|
176
|
+
this.#notify(sessionId, previous?.text !== void 0 && text.startsWith(previous.text) ? {
|
|
177
|
+
kind: "text",
|
|
178
|
+
...base,
|
|
179
|
+
append: text.slice(previous.text.length)
|
|
180
|
+
} : {
|
|
181
|
+
kind: "text",
|
|
182
|
+
...base,
|
|
183
|
+
text
|
|
184
|
+
});
|
|
185
|
+
this.#scheduleTextFlush(sessionId);
|
|
186
|
+
}
|
|
187
|
+
/** Persist any pending streaming transcript now (segment close, turn end). */
|
|
188
|
+
flushTranscriptText(sessionId) {
|
|
189
|
+
const timer = this.#flushTimers.get(sessionId);
|
|
190
|
+
if (timer !== void 0) {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
this.#flushTimers.delete(sessionId);
|
|
193
|
+
}
|
|
194
|
+
return this.#flushLive(sessionId);
|
|
195
|
+
}
|
|
196
|
+
#notify(sessionId, delta) {
|
|
197
|
+
const set = this.#listeners.get(sessionId);
|
|
198
|
+
if (set === void 0) return;
|
|
199
|
+
for (const listener of [...set]) try {
|
|
200
|
+
listener(delta);
|
|
201
|
+
} catch {}
|
|
202
|
+
}
|
|
203
|
+
#merged(sessionId, base) {
|
|
204
|
+
const overlay = this.#live.get(sessionId);
|
|
205
|
+
const boost = this.#boost.get(sessionId) ?? 0;
|
|
206
|
+
if ((overlay === void 0 || overlay.size === 0) && boost === 0) return base;
|
|
207
|
+
return {
|
|
208
|
+
...base,
|
|
209
|
+
revision: base.revision + boost,
|
|
210
|
+
...overlay === void 0 || overlay.size === 0 ? {} : { activities: mergeActivities(base.activities, [...overlay.values()]) }
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async #base(sessionId) {
|
|
214
|
+
const cached = this.#latest.get(sessionId);
|
|
215
|
+
if (cached !== void 0) return cached;
|
|
216
|
+
const loaded = await this.#readNow(sessionId);
|
|
217
|
+
this.#latest.set(sessionId, loaded);
|
|
218
|
+
return loaded;
|
|
219
|
+
}
|
|
220
|
+
#scheduleTextFlush(sessionId) {
|
|
221
|
+
if (this.#flushTimers.has(sessionId)) return;
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
this.#flushTimers.delete(sessionId);
|
|
224
|
+
this.#flushLive(sessionId);
|
|
225
|
+
}, TEXT_FLUSH_MS);
|
|
226
|
+
timer.unref?.();
|
|
227
|
+
this.#flushTimers.set(sessionId, timer);
|
|
228
|
+
}
|
|
229
|
+
async #flushLive(sessionId) {
|
|
230
|
+
const overlay = this.#live.get(sessionId);
|
|
231
|
+
if (overlay === void 0 || overlay.size === 0) return;
|
|
232
|
+
const entries = [...overlay.entries()];
|
|
233
|
+
try {
|
|
234
|
+
await this.#update(sessionId, (current) => ({
|
|
235
|
+
...current,
|
|
236
|
+
activities: mergeActivities(current.activities, entries.map(([, value]) => value))
|
|
237
|
+
}));
|
|
238
|
+
} catch {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (const [key, value] of entries) if (overlay.get(key) === value) overlay.delete(key);
|
|
242
|
+
if (overlay.size === 0) this.#live.delete(sessionId);
|
|
127
243
|
}
|
|
128
244
|
writeBinding(sessionId, value) {
|
|
129
245
|
const normalized = normalizeBinding(value);
|
|
@@ -137,21 +253,30 @@ var ClaudeSidecarRepository = class {
|
|
|
137
253
|
return this.#update(sessionId, (current) => ({
|
|
138
254
|
...current,
|
|
139
255
|
activities: mergeActivities(current.activities, [normalized])
|
|
140
|
-
})
|
|
256
|
+
}), false, {
|
|
257
|
+
kind: "activity",
|
|
258
|
+
activity: normalized
|
|
259
|
+
});
|
|
141
260
|
}
|
|
142
261
|
writeContextUsage(sessionId, value) {
|
|
143
262
|
const normalized = normalizeContextUsage(value);
|
|
144
263
|
return this.#update(sessionId, (current) => ({
|
|
145
264
|
...current,
|
|
146
265
|
contextUsage: normalized
|
|
147
|
-
})
|
|
266
|
+
}), false, {
|
|
267
|
+
kind: "contextUsage",
|
|
268
|
+
value: normalized
|
|
269
|
+
});
|
|
148
270
|
}
|
|
149
271
|
writeTasks(sessionId, value) {
|
|
150
272
|
const normalized = normalizeTasksEvent(value);
|
|
151
273
|
return this.#update(sessionId, (current) => ({
|
|
152
274
|
...current,
|
|
153
275
|
tasks: normalized
|
|
154
|
-
})
|
|
276
|
+
}), false, {
|
|
277
|
+
kind: "tasks",
|
|
278
|
+
value: normalized
|
|
279
|
+
});
|
|
155
280
|
}
|
|
156
281
|
importLegacy(sessionId, events) {
|
|
157
282
|
const importedActivities = events.filter((event) => event.type === CLAUDE_ACTIVITY_EVENT).map((event) => activity(event.data)).filter((item) => item !== void 0);
|
|
@@ -164,15 +289,15 @@ var ClaudeSidecarRepository = class {
|
|
|
164
289
|
...current.binding !== void 0 || importedBinding === void 0 ? {} : { binding: normalizeBinding(importedBinding) },
|
|
165
290
|
...current.contextUsage !== void 0 || importedUsage === void 0 ? {} : { contextUsage: normalizeContextUsage(importedUsage) },
|
|
166
291
|
...current.tasks !== void 0 || importedTasks === void 0 ? {} : { tasks: normalizeTasksEvent(importedTasks.tasks) }
|
|
167
|
-
}), true);
|
|
292
|
+
}), true, { kind: "sync" });
|
|
168
293
|
}
|
|
169
294
|
#path(sessionId, root = this.root) {
|
|
170
295
|
if (sessionId.length === 0 || sessionId.length > 1024) throw new Error("dsh-claude: invalid session id");
|
|
171
296
|
return join(root, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
172
297
|
}
|
|
173
|
-
#update(sessionId, change, skipUnchanged = false) {
|
|
298
|
+
#update(sessionId, change, skipUnchanged = false, delta) {
|
|
174
299
|
const operation = (this.#pending.get(sessionId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
175
|
-
const current = await this.#
|
|
300
|
+
const current = await this.#base(sessionId);
|
|
176
301
|
const changed = parseClaudeSidecar({
|
|
177
302
|
...change(current),
|
|
178
303
|
schemaVersion: SIDECAR_SCHEMA_VERSION,
|
|
@@ -184,6 +309,8 @@ var ClaudeSidecarRepository = class {
|
|
|
184
309
|
revision: current.revision + 1
|
|
185
310
|
};
|
|
186
311
|
await this.#writeNow(sessionId, next);
|
|
312
|
+
this.#latest.set(sessionId, next);
|
|
313
|
+
if (delta !== void 0) this.#notify(sessionId, delta);
|
|
187
314
|
return next;
|
|
188
315
|
});
|
|
189
316
|
this.#pending.set(sessionId, operation);
|
|
@@ -594,7 +721,9 @@ function normalizeAssistant(message) {
|
|
|
594
721
|
return normalized;
|
|
595
722
|
}
|
|
596
723
|
function normalizeUser(message) {
|
|
724
|
+
if (message.isReplay === true) return [];
|
|
597
725
|
const content = record$4(message.message)?.content;
|
|
726
|
+
if (typeof content === "string") return [];
|
|
598
727
|
if (!Array.isArray(content)) return [{
|
|
599
728
|
kind: "protocol-error",
|
|
600
729
|
title: "Malformed Claude user message",
|
|
@@ -1647,6 +1776,7 @@ var ClaudeSupervisor = class {
|
|
|
1647
1776
|
if (entry.active !== active) return;
|
|
1648
1777
|
if (active.aborted) {
|
|
1649
1778
|
await this.#upsertTranscriptText(active);
|
|
1779
|
+
await this.#flushTranscript(active);
|
|
1650
1780
|
await this.#appendSafely(active, {
|
|
1651
1781
|
kind: "status",
|
|
1652
1782
|
phase: "failed",
|
|
@@ -1689,6 +1819,7 @@ var ClaudeSupervisor = class {
|
|
|
1689
1819
|
});
|
|
1690
1820
|
}
|
|
1691
1821
|
await this.#upsertTranscriptText(active);
|
|
1822
|
+
await this.#flushTranscript(active);
|
|
1692
1823
|
const message = result.errors?.join("\n") ?? (result.terminalReason !== void 0 ? `Claude Code failed the turn (${result.terminalReason})` : "Claude Code failed the turn");
|
|
1693
1824
|
await this.#appendSafely(active, {
|
|
1694
1825
|
kind: "error",
|
|
@@ -1724,6 +1855,7 @@ var ClaudeSupervisor = class {
|
|
|
1724
1855
|
phase: "completed",
|
|
1725
1856
|
title: "Claude Code turn completed"
|
|
1726
1857
|
});
|
|
1858
|
+
await this.#flushTranscript(active);
|
|
1727
1859
|
active.output.push({
|
|
1728
1860
|
type: "complete",
|
|
1729
1861
|
text: active.text
|
|
@@ -1741,9 +1873,7 @@ var ClaudeSupervisor = class {
|
|
|
1741
1873
|
const ordinal = active.transcriptTextOrdinal ?? active.cursor.nextOrdinal++;
|
|
1742
1874
|
active.transcriptTextOrdinal = ordinal;
|
|
1743
1875
|
try {
|
|
1744
|
-
|
|
1745
|
-
kind: "text",
|
|
1746
|
-
phase: "updated",
|
|
1876
|
+
this.#sidecar.appendTranscriptText(active.agent.id, {
|
|
1747
1877
|
text: active.transcriptText,
|
|
1748
1878
|
turn: active.cursor.turn,
|
|
1749
1879
|
step: active.cursor.step,
|
|
@@ -1751,7 +1881,11 @@ var ClaudeSupervisor = class {
|
|
|
1751
1881
|
});
|
|
1752
1882
|
} catch {}
|
|
1753
1883
|
}
|
|
1884
|
+
async #flushTranscript(active) {
|
|
1885
|
+
await this.#sidecar.flushTranscriptText(active.agent.id).catch(() => void 0);
|
|
1886
|
+
}
|
|
1754
1887
|
#closeTranscriptTextSegment(active) {
|
|
1888
|
+
this.#flushTranscript(active);
|
|
1755
1889
|
active.transcriptText = "";
|
|
1756
1890
|
active.transcriptTextOrdinal = void 0;
|
|
1757
1891
|
}
|
|
@@ -1807,6 +1941,7 @@ var ClaudeSupervisor = class {
|
|
|
1807
1941
|
const stderr = entry.process?.stderrTail();
|
|
1808
1942
|
if (active !== void 0) {
|
|
1809
1943
|
await this.#upsertTranscriptText(active);
|
|
1944
|
+
await this.#flushTranscript(active);
|
|
1810
1945
|
if (active.signal !== void 0 && active.abortListener !== void 0) active.signal.removeEventListener("abort", active.abortListener);
|
|
1811
1946
|
const unknown = active.sawActivity;
|
|
1812
1947
|
entry.state = unknown ? "outcome-unknown" : "disconnected";
|
|
@@ -2376,47 +2511,173 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2376
2511
|
//#endregion
|
|
2377
2512
|
//#region src/projection-routes.ts
|
|
2378
2513
|
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
2379
|
-
|
|
2514
|
+
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
2515
|
+
* the transcript hot path so git/gh latency never delays visible text. */
|
|
2516
|
+
const META_REFRESH_MS = 5e3;
|
|
2517
|
+
function targetFromUrl(rawUrl) {
|
|
2380
2518
|
try {
|
|
2381
2519
|
const pathname = new URL(rawUrl ?? "/", "http://localhost").pathname;
|
|
2382
2520
|
const prefix = `${CLAUDE_PROJECTION_PATH}/`;
|
|
2383
2521
|
if (!pathname.startsWith(prefix)) return void 0;
|
|
2384
|
-
|
|
2522
|
+
let encoded = pathname.slice(prefix.length);
|
|
2523
|
+
const stream = encoded.endsWith("/stream");
|
|
2524
|
+
if (stream) encoded = encoded.slice(0, -7);
|
|
2385
2525
|
if (encoded.length === 0 || encoded.includes("/")) return void 0;
|
|
2386
2526
|
const sessionId = decodeURIComponent(encoded);
|
|
2387
2527
|
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$2) return void 0;
|
|
2388
|
-
return
|
|
2528
|
+
return {
|
|
2529
|
+
sessionId,
|
|
2530
|
+
stream
|
|
2531
|
+
};
|
|
2389
2532
|
} catch {
|
|
2390
2533
|
return;
|
|
2391
2534
|
}
|
|
2392
2535
|
}
|
|
2393
|
-
|
|
2536
|
+
function envelope(projection, meta) {
|
|
2537
|
+
return {
|
|
2538
|
+
schemaVersion: projection.schemaVersion,
|
|
2539
|
+
revision: projection.revision,
|
|
2540
|
+
owned: meta.owned,
|
|
2541
|
+
commands: meta.commands,
|
|
2542
|
+
activities: projection.activities,
|
|
2543
|
+
...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
|
|
2544
|
+
...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
|
|
2545
|
+
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
2546
|
+
reviewComments: meta.reviewComments
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
/** Register the browser-readable, credential-free sidecar projection endpoint.
|
|
2550
|
+
* `GET <path>/:sessionId` returns one snapshot; `GET <path>/:sessionId/stream`
|
|
2551
|
+
* returns an NDJSON stream: a full snapshot line followed by incremental
|
|
2552
|
+
* transcript/activity deltas and periodic metadata/heartbeat lines. */
|
|
2394
2553
|
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => []) {
|
|
2554
|
+
const info = (message) => {
|
|
2555
|
+
ctx.logger?.info?.(message);
|
|
2556
|
+
};
|
|
2557
|
+
const assembleMeta = async (sessionId) => {
|
|
2558
|
+
const owned = ownsSession(sessionId);
|
|
2559
|
+
const repository = owned ? await repositoryForSession(sessionId) : void 0;
|
|
2560
|
+
return {
|
|
2561
|
+
owned,
|
|
2562
|
+
commands: commandsForSession(sessionId),
|
|
2563
|
+
...repository === void 0 ? {} : { repository },
|
|
2564
|
+
reviewComments: owned ? reviewCommentsForSession(sessionId) : []
|
|
2565
|
+
};
|
|
2566
|
+
};
|
|
2567
|
+
const streamProjection = async (res, sessionId) => {
|
|
2568
|
+
info(`dsh-claude: projection stream opened for ${sessionId.slice(0, 64)}`);
|
|
2569
|
+
let textDeltas = 0;
|
|
2570
|
+
let textBytes = 0;
|
|
2571
|
+
let textSince = Date.now();
|
|
2572
|
+
let meta = await assembleMeta(sessionId);
|
|
2573
|
+
res.writeHead(200, {
|
|
2574
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
2575
|
+
"cache-control": "no-store",
|
|
2576
|
+
"x-content-type-options": "nosniff"
|
|
2577
|
+
});
|
|
2578
|
+
res.flushHeaders?.();
|
|
2579
|
+
let closed = false;
|
|
2580
|
+
const writeLine = (value) => {
|
|
2581
|
+
if (closed) return;
|
|
2582
|
+
try {
|
|
2583
|
+
res.write(`${JSON.stringify(value)}\n`);
|
|
2584
|
+
} catch {
|
|
2585
|
+
closed = true;
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
const writeSnapshot = async () => {
|
|
2589
|
+
const projection = await sidecar.read(sessionId);
|
|
2590
|
+
writeLine({
|
|
2591
|
+
type: "snapshot",
|
|
2592
|
+
...envelope(projection, meta)
|
|
2593
|
+
});
|
|
2594
|
+
};
|
|
2595
|
+
await writeSnapshot();
|
|
2596
|
+
const unsubscribe = sidecar.subscribe(sessionId, (delta) => {
|
|
2597
|
+
switch (delta.kind) {
|
|
2598
|
+
case "text":
|
|
2599
|
+
textDeltas += 1;
|
|
2600
|
+
textBytes += (delta.append ?? delta.text ?? "").length;
|
|
2601
|
+
if (textDeltas % 25 === 0) {
|
|
2602
|
+
const elapsed = Date.now() - textSince;
|
|
2603
|
+
info(`dsh-claude: stream ${sessionId.slice(0, 24)} 25 text deltas ${textBytes}B in ${elapsed}ms`);
|
|
2604
|
+
textBytes = 0;
|
|
2605
|
+
textSince = Date.now();
|
|
2606
|
+
}
|
|
2607
|
+
writeLine({
|
|
2608
|
+
type: "text",
|
|
2609
|
+
turn: delta.turn,
|
|
2610
|
+
step: delta.step,
|
|
2611
|
+
ordinal: delta.ordinal,
|
|
2612
|
+
...delta.append === void 0 ? {} : { append: delta.append },
|
|
2613
|
+
...delta.text === void 0 ? {} : { text: delta.text }
|
|
2614
|
+
});
|
|
2615
|
+
return;
|
|
2616
|
+
case "activity":
|
|
2617
|
+
writeLine({
|
|
2618
|
+
type: "activity",
|
|
2619
|
+
activity: delta.activity
|
|
2620
|
+
});
|
|
2621
|
+
return;
|
|
2622
|
+
case "contextUsage":
|
|
2623
|
+
writeLine({
|
|
2624
|
+
type: "contextUsage",
|
|
2625
|
+
value: delta.value
|
|
2626
|
+
});
|
|
2627
|
+
return;
|
|
2628
|
+
case "tasks":
|
|
2629
|
+
writeLine({
|
|
2630
|
+
type: "tasks",
|
|
2631
|
+
value: delta.value
|
|
2632
|
+
});
|
|
2633
|
+
return;
|
|
2634
|
+
case "sync": writeSnapshot().catch(() => void 0);
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
const timer = setInterval(() => {
|
|
2638
|
+
(async () => {
|
|
2639
|
+
const next = await assembleMeta(sessionId);
|
|
2640
|
+
if (closed) return;
|
|
2641
|
+
if (JSON.stringify(next) === JSON.stringify(meta)) {
|
|
2642
|
+
writeLine({ type: "ping" });
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
meta = next;
|
|
2646
|
+
writeLine({
|
|
2647
|
+
type: "meta",
|
|
2648
|
+
owned: meta.owned,
|
|
2649
|
+
commands: meta.commands,
|
|
2650
|
+
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
2651
|
+
reviewComments: meta.reviewComments
|
|
2652
|
+
});
|
|
2653
|
+
})().catch(() => void 0);
|
|
2654
|
+
}, META_REFRESH_MS);
|
|
2655
|
+
timer.unref?.();
|
|
2656
|
+
await new Promise((resolve) => {
|
|
2657
|
+
res.on("close", () => {
|
|
2658
|
+
closed = true;
|
|
2659
|
+
clearInterval(timer);
|
|
2660
|
+
unsubscribe();
|
|
2661
|
+
info(`dsh-claude: projection stream closed for ${sessionId.slice(0, 64)} after ${textDeltas} text deltas`);
|
|
2662
|
+
resolve();
|
|
2663
|
+
});
|
|
2664
|
+
});
|
|
2665
|
+
};
|
|
2395
2666
|
ctx.effect(() => ctx.webServer.register({
|
|
2396
2667
|
kind: "prefix",
|
|
2397
2668
|
path: CLAUDE_PROJECTION_PATH,
|
|
2398
2669
|
handler: async (req, res) => {
|
|
2399
2670
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
2400
2671
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
2401
|
-
const
|
|
2402
|
-
if (
|
|
2672
|
+
const target = targetFromUrl(req.url);
|
|
2673
|
+
if (target === void 0) return json(res, 400, { error: "invalid session id" });
|
|
2403
2674
|
try {
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
return json(res, 200, {
|
|
2408
|
-
schemaVersion: projection.schemaVersion,
|
|
2409
|
-
revision: projection.revision,
|
|
2410
|
-
owned,
|
|
2411
|
-
commands: commandsForSession(sessionId),
|
|
2412
|
-
activities: projection.activities,
|
|
2413
|
-
...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
|
|
2414
|
-
...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
|
|
2415
|
-
...repository === void 0 ? {} : { repository },
|
|
2416
|
-
reviewComments: owned ? reviewCommentsForSession(sessionId) : []
|
|
2417
|
-
});
|
|
2675
|
+
if (target.stream) return await streamProjection(res, target.sessionId);
|
|
2676
|
+
info(`dsh-claude: projection poll for ${target.sessionId.slice(0, 64)}`);
|
|
2677
|
+
return json(res, 200, envelope(await sidecar.read(target.sessionId), await assembleMeta(target.sessionId)));
|
|
2418
2678
|
} catch {
|
|
2419
|
-
return json(res, 500, { error: "projection unavailable" });
|
|
2679
|
+
if (!res.headersSent) return json(res, 500, { error: "projection unavailable" });
|
|
2680
|
+
res.end();
|
|
2420
2681
|
}
|
|
2421
2682
|
}
|
|
2422
2683
|
}), "dsh-claude: sidecar projection route");
|