{"version":3,"file":"mode-service.mjs","names":[],"sources":["../../../src/services/mode-service.ts"],"sourcesContent":["/**\n * Mode service — tracks per-user safety mode and signing mode.\n *\n * Two independent toggles:\n * 1. Intent confirmation: safe (confirm before acting) / danger (act immediately) / readonly (view only, no writes)\n * 2. Signing method: wallet (WalletConnect, phone approval) / autosign (private key)\n *\n * State persists on volume alongside onboarding state.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type SafetyMode = 'safe' | 'danger' | 'readonly';\nexport type SigningMode = 'wallet' | 'autosign';\n\nexport interface UserMode {\n  userId: string;\n  safetyMode: SafetyMode;\n  signingMode: SigningMode;\n  lastChanged: number;\n}\n\nfunction getStateDir(): string {\n  return process.env.OPENCLAWNCH_TX_DIR\n    ? join(process.env.OPENCLAWNCH_TX_DIR, '..', 'modes')\n    : join(process.env.HOME ?? '/tmp', '.openclawnch', 'modes');\n}\n\nfunction ensureDir(): void {\n  const dir = getStateDir();\n  if (!existsSync(dir)) {\n    mkdirSync(dir, { recursive: true });\n  }\n}\n\n// M5: Sanitize userId to prevent path traversal\nfunction sanitizeUserId(userId: string): string {\n  // Only allow alphanumeric, underscores, hyphens, dots\n  const safe = userId.replace(/[^a-zA-Z0-9_\\-\\.]/g, '_');\n  // Prevent directory traversal\n  if (safe.includes('..') || safe.includes('/') || safe.includes('\\\\')) {\n    return 'invalid_user';\n  }\n  return safe.slice(0, 64); // Cap length\n}\n\nfunction modePath(userId: string): string {\n  return join(getStateDir(), `${sanitizeUserId(userId)}.json`);\n}\n\nfunction loadMode(userId: string): UserMode {\n  try {\n    const path = modePath(userId);\n    if (existsSync(path)) {\n      return JSON.parse(readFileSync(path, 'utf8')) as UserMode;\n    }\n  } catch { /* default */ }\n  return {\n    userId,\n    safetyMode: 'safe',\n    signingMode: 'wallet',\n    lastChanged: Date.now(),\n  };\n}\n\nfunction saveMode(mode: UserMode): void {\n  ensureDir();\n  writeFileSync(modePath(mode.userId), JSON.stringify(mode, null, 2), 'utf8');\n}\n\nconst cache = new Map<string, UserMode>();\n\nexport function getUserMode(userId: string): UserMode {\n  let mode = cache.get(userId);\n  if (!mode) {\n    mode = loadMode(userId);\n    cache.set(userId, mode);\n  }\n  return mode;\n}\n\nexport function setSafetyMode(userId: string, safetyMode: SafetyMode): UserMode {\n  const mode = getUserMode(userId);\n  mode.safetyMode = safetyMode;\n  mode.lastChanged = Date.now();\n  cache.set(userId, mode);\n  saveMode(mode);\n  return mode;\n}\n\nexport function setSigningMode(userId: string, signingMode: SigningMode): UserMode {\n  const mode = getUserMode(userId);\n  mode.signingMode = signingMode;\n  mode.lastChanged = Date.now();\n  cache.set(userId, mode);\n  saveMode(mode);\n  return mode;\n}\n\n/**\n * Check if a user is in readonly mode (no on-chain writes allowed).\n */\nexport function isReadonly(userId: string): boolean {\n  return getUserMode(userId).safetyMode === 'readonly';\n}\n\nexport function resetModes(): void {\n  cache.clear();\n}\n"],"mappings":";;;;;;;;;;;;AAuBA,SAAS,cAAsB;AAC7B,QAAO,QAAQ,IAAI,qBACf,KAAK,QAAQ,IAAI,oBAAoB,MAAM,QAAQ,GACnD,KAAK,QAAQ,IAAI,QAAQ,QAAQ,gBAAgB,QAAQ;;AAG/D,SAAS,YAAkB;CACzB,MAAM,MAAM,aAAa;AACzB,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;;AAKvC,SAAS,eAAe,QAAwB;CAE9C,MAAM,OAAO,OAAO,QAAQ,sBAAsB,IAAI;AAEtD,KAAI,KAAK,SAAS,KAAK,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,KAAK,CAClE,QAAO;AAET,QAAO,KAAK,MAAM,GAAG,GAAG;;AAG1B,SAAS,SAAS,QAAwB;AACxC,QAAO,KAAK,aAAa,EAAE,GAAG,eAAe,OAAO,CAAC,OAAO;;AAG9D,SAAS,SAAS,QAA0B;AAC1C,KAAI;EACF,MAAM,OAAO,SAAS,OAAO;AAC7B,MAAI,WAAW,KAAK,CAClB,QAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;SAEzC;AACR,QAAO;EACL;EACA,YAAY;EACZ,aAAa;EACb,aAAa,KAAK,KAAK;EACxB;;AAGH,SAAS,SAAS,MAAsB;AACtC,YAAW;AACX,eAAc,SAAS,KAAK,OAAO,EAAE,KAAK,UAAU,MAAM,MAAM,EAAE,EAAE,OAAO;;AAG7E,MAAM,wBAAQ,IAAI,KAAuB;AAEzC,SAAgB,YAAY,QAA0B;CACpD,IAAI,OAAO,MAAM,IAAI,OAAO;AAC5B,KAAI,CAAC,MAAM;AACT,SAAO,SAAS,OAAO;AACvB,QAAM,IAAI,QAAQ,KAAK;;AAEzB,QAAO;;AAGT,SAAgB,cAAc,QAAgB,YAAkC;CAC9E,MAAM,OAAO,YAAY,OAAO;AAChC,MAAK,aAAa;AAClB,MAAK,cAAc,KAAK,KAAK;AAC7B,OAAM,IAAI,QAAQ,KAAK;AACvB,UAAS,KAAK;AACd,QAAO;;AAGT,SAAgB,eAAe,QAAgB,aAAoC;CACjF,MAAM,OAAO,YAAY,OAAO;AAChC,MAAK,cAAc;AACnB,MAAK,cAAc,KAAK,KAAK;AAC7B,OAAM,IAAI,QAAQ,KAAK;AACvB,UAAS,KAAK;AACd,QAAO;;;;;AAMT,SAAgB,WAAW,QAAyB;AAClD,QAAO,YAAY,OAAO,CAAC,eAAe;;AAG5C,SAAgB,aAAmB;AACjC,OAAM,OAAO"}