@jacexh/claude-web-console 0.7.8 → 0.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacexh/claude-web-console",
3
- "version": "0.7.8",
3
+ "version": "0.7.10",
4
4
  "description": "Web-based console for Claude Code — manage sessions, switch models, preview artifacts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,7 +11,7 @@ import {
11
11
  type SDKMessage,
12
12
  type PermissionResult,
13
13
  } from '@anthropic-ai/claude-agent-sdk'
14
- import { readFileSync, readdirSync, statSync } from 'node:fs'
14
+ import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'node:fs'
15
15
  import { join } from 'node:path'
16
16
  import { homedir } from 'node:os'
17
17
  import type { FastifyBaseLogger } from 'fastify'
@@ -57,16 +57,16 @@ function cleanEnv(cwd?: string): Record<string, string | undefined> {
57
57
  }
58
58
 
59
59
  const CLAUDE_EXECUTABLE = process.env.CLAUDE_PATH ?? 'claude'
60
+ const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude')
60
61
 
61
62
  // Read ~/.claude/settings.json and resolve enabled plugins to local --plugin-dir paths
62
63
  function getPluginDirArgs(): string[] {
63
- const home = homedir()
64
64
  try {
65
- const settings = JSON.parse(readFileSync(join(home, '.claude', 'settings.json'), 'utf-8'))
65
+ const settings = JSON.parse(readFileSync(join(CLAUDE_DIR, 'settings.json'), 'utf-8'))
66
66
  const enabled = settings.enabledPlugins as Record<string, boolean> | undefined
67
67
  if (!enabled) return []
68
68
  const args: string[] = []
69
- const pluginsBase = join(home, '.claude', 'plugins')
69
+ const pluginsBase = join(CLAUDE_DIR, 'plugins')
70
70
  for (const [key, value] of Object.entries(enabled)) {
71
71
  if (!value) continue
72
72
  // key format: "plugin-name@marketplace-id"
@@ -103,7 +103,7 @@ function getPluginDirArgs(): string[] {
103
103
 
104
104
  /** Check if a session is being used by an external process (e.g. CLI) */
105
105
  function isSessionLockedExternally(sessionId: string): boolean {
106
- const sessionsDir = join(homedir(), '.claude', 'sessions')
106
+ const sessionsDir = join(CLAUDE_DIR, 'sessions')
107
107
  try {
108
108
  const files = readdirSync(sessionsDir).filter((f) => f.endsWith('.json'))
109
109
  for (const file of files) {
@@ -119,6 +119,28 @@ function isSessionLockedExternally(sessionId: string): boolean {
119
119
  return false
120
120
  }
121
121
 
122
+ const optionsDir = join(CLAUDE_DIR, 'claude-web-console')
123
+
124
+ type SessionCreationOptions = {
125
+ model?: string
126
+ permissionMode?: string
127
+ executableArgs?: string[]
128
+ env?: Record<string, string>
129
+ }
130
+
131
+ function saveSessionOptions(sessionId: string, opts: SessionCreationOptions): void {
132
+ try {
133
+ mkdirSync(optionsDir, { recursive: true })
134
+ writeFileSync(join(optionsDir, `${sessionId}.options.json`), JSON.stringify(opts))
135
+ } catch { /* best-effort */ }
136
+ }
137
+
138
+ function loadSessionOptions(sessionId: string): SessionCreationOptions | undefined {
139
+ try {
140
+ return JSON.parse(readFileSync(join(optionsDir, `${sessionId}.options.json`), 'utf-8'))
141
+ } catch { return undefined }
142
+ }
143
+
122
144
  export class SessionManager {
123
145
  private log: FastifyBaseLogger
124
146
  private sessions = new Map<string, SDKSession>()
@@ -133,12 +155,7 @@ export class SessionManager {
133
155
  // Track cwd for each session so we can resume in the correct project
134
156
  private sessionCwds = new Map<string, string>()
135
157
  /** User-supplied creation options that must survive resume cycles */
136
- private sessionCreationOptions = new Map<string, {
137
- model?: string
138
- permissionMode?: string
139
- executableArgs?: string[]
140
- env?: Record<string, string>
141
- }>()
158
+ private sessionCreationOptions = new Map<string, SessionCreationOptions>()
142
159
  // Cache commands extracted from SDK init messages
143
160
  private sessionCommands = new Map<string, { name: string; description: string }[]>()
144
161
  // Active stream (Query) references for control requests like setModel
@@ -491,7 +508,7 @@ export class SessionManager {
491
508
  const c = this.sessionCwds.get(tempId)
492
509
  if (c) { this.sessionCwds.delete(tempId); this.sessionCwds.set(sessionId, c) }
493
510
  const opts = this.sessionCreationOptions.get(tempId)
494
- if (opts) { this.sessionCreationOptions.delete(tempId); this.sessionCreationOptions.set(sessionId, opts) }
511
+ if (opts) { this.sessionCreationOptions.delete(tempId); this.sessionCreationOptions.set(sessionId, opts); saveSessionOptions(sessionId, opts) }
495
512
  const listeners = this.sessionListeners.get(tempId)
496
513
  if (listeners) { this.sessionListeners.delete(tempId); this.sessionListeners.set(sessionId, listeners) }
497
514
  this.streamingSessionIds.delete(tempId)
@@ -746,9 +763,13 @@ export class SessionManager {
746
763
  }
747
764
  this.closedSessionIds.delete(sessionId)
748
765
 
749
- // Use the cached cwd and creation options so claude spawns with the correct config
766
+ // Use the cached cwd and creation options so claude spawns with the correct config.
767
+ // Fall back to disk if the in-memory cache was lost (e.g. server restart).
750
768
  const cwd = this.sessionCwds.get(sessionId)
751
- const cached = this.sessionCreationOptions.get(sessionId)
769
+ const cached = this.sessionCreationOptions.get(sessionId) ?? loadSessionOptions(sessionId)
770
+ if (cached && !this.sessionCreationOptions.has(sessionId)) {
771
+ this.sessionCreationOptions.set(sessionId, cached)
772
+ }
752
773
  const resumeSessionIdRef = { current: sessionId }
753
774
  const pluginArgs = getPluginDirArgs()
754
775
  const userArgs = cached?.executableArgs ?? []