@buzzni/saycode-cli 0.9.2 → 0.9.3

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/index.mjs CHANGED
@@ -2,13 +2,13 @@
2
2
  import { createInterface } from "node:readline";
3
3
  import { homedir, tmpdir, platform, totalmem, freemem, cpus, userInfo, hostname as hostname$1 } from "node:os";
4
4
  import fs, { readFileSync, mkdirSync, writeFileSync, rmSync, chmodSync, constants as constants$3, appendFileSync, existsSync, realpathSync, createWriteStream, openSync, fstatSync, readSync, closeSync, accessSync, statSync } from "node:fs";
5
- import path$1, { dirname, join, basename, extname, delimiter, win32, resolve, sep, relative, isAbsolute } from "node:path";
5
+ import path$1, { dirname, join, basename, extname, delimiter, win32, resolve, sep, relative, isAbsolute, posix } from "node:path";
6
6
  import { createHash, randomUUID, randomBytes, generateKeyPairSync, sign, createHmac, timingSafeEqual, createPublicKey, verify } from "node:crypto";
7
7
  import require$$0$5, { Buffer as Buffer$1 } from "buffer";
8
8
  import { readFile, stat, mkdtemp, access, rm, mkdir, appendFile, writeFile, chmod, lstat, open, readdir, rename, statfs, realpath } from "node:fs/promises";
9
9
  import { execFile, spawn } from "node:child_process";
10
10
  import net, { isIP, connect as connect$1, createServer as createServer$1 } from "node:net";
11
- import { createServer, request } from "node:http";
11
+ import { createServer, request as request$1 } from "node:http";
12
12
  import { promisify } from "node:util";
13
13
  import { StringDecoder } from "node:string_decoder";
14
14
  import require$$0$6 from "stream";
@@ -49,7 +49,7 @@ function _mergeNamespaces(n, m) {
49
49
  }
50
50
  return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" }));
51
51
  }
52
- const version$3 = "0.9.2";
52
+ const version$3 = "0.9.3";
53
53
  const manifest = {
54
54
  version: version$3
55
55
  };
@@ -442,6 +442,52 @@ const conversationTabsKo = {
442
442
  "workspaceHost.closeConversationHint": "탭만 닫고 대화 기록은 남아요.",
443
443
  "workspaceHost.newConversation": "새 대화 열기"
444
444
  };
445
+ const personalAiKo = {
446
+ "aiConnection.sectionTitle": "연결된 AI 확인하기",
447
+ "aiConnection.ai": "AI",
448
+ "aiConnection.pc": "PC",
449
+ "aiConnection.selectPc": "PC 선택",
450
+ "aiConnection.pcUnknown": "확인할 수 없음",
451
+ "aiConnection.selectAi": "AI 선택",
452
+ "aiConnection.chooseAiHint": "AI를 선택하면 이 PC의 연결 상태를 확인할 수 있어요.",
453
+ "personalAi.settingsTitle": "내 AI 계정",
454
+ "personalAi.settingsDescription": "사용 중인 ChatGPT 또는 Claude 계정을 연결하세요.",
455
+ "personalAi.title": "개인 AI 계정 연결",
456
+ "personalAi.imageAlt": "웹 계정과 데스크탑 대화 화면이 연결되는 모습",
457
+ "personalAi.intro": "ChatGPT 또는 Claude 계정을 연결하면\nSayCode에서 AI와 대화할 수 있어요.",
458
+ "personalAi.note": "사용 중인 AI 계정으로 로그인해 주세요.",
459
+ "personalAi.later": "나중에 하기",
460
+ "personalAi.connect": "내 AI 계정 연결",
461
+ "personalAi.choose": "연결할 AI를 선택해 주세요",
462
+ "personalAi.chooseDescription": "선택한 서비스의 웹사이트에서 로그인합니다.",
463
+ "personalAi.withProvider": "{provider} 계정으로 연결",
464
+ "personalAi.back": "뒤로",
465
+ "personalAi.providerTitle": "{provider} 계정 연결",
466
+ "personalAi.waiting": "브라우저에서 로그인을 완료해 주세요.",
467
+ "personalAi.approval": "로그인과 연결 승인을 마치면\n자동으로 연결 상태를 확인합니다.",
468
+ "personalAi.cancel": "취소",
469
+ "personalAi.reopen": "브라우저 다시 열기",
470
+ "personalAi.expired": "아직 연결을 확인하지 못했어요.",
471
+ "personalAi.retryDescription": "다시 연결해 브라우저에서\n로그인을 완료해 주세요.",
472
+ "personalAi.retry": "다시 연결",
473
+ "personalAi.connected": "{provider} 계정이 연결됐어요.",
474
+ "personalAi.ready": "이 컴퓨터에서 사용할 준비가 됐습니다.",
475
+ "personalAi.done": "완료",
476
+ "personalAi.localOnly": "개인 AI 계정은 이 컴퓨터에서 연결할 수 있어요. 작업PC를 이 컴퓨터로 선택한 뒤 다시 연결해 주세요.",
477
+ "personalAi.unavailable": "이 컴퓨터의 연결과 AI 도구 설치를 확인한 뒤 다시 열어 주세요.",
478
+ "aiConnection.loginLinkFailed": "로그인 링크를 열지 못했어요. 터미널 보기에서 링크를 직접 눌러 주세요.",
479
+ "aiConnection.openLoginLink": "로그인 링크 열기",
480
+ "aiConnection.loginPreparing": "로그인 준비 중이에요…",
481
+ "aiConnection.codeLabel": "브라우저에서 로그인한 뒤 표시되는 인증 코드를 붙여 넣어 주세요.",
482
+ "aiConnection.codeSend": "인증 코드 보내기",
483
+ "aiConnection.loginFailed": "로그인하지 못했어요. 인증 코드를 다시 확인한 뒤 다시 시도해 주세요.",
484
+ "aiConnection.loginRetry": "다시 시도",
485
+ "aiConnection.loginTerminalFailed": "로그인 터미널을 열지 못했어요. 아래 터미널에서 다시 열어 주세요.",
486
+ "aiConnection.loginTerminalClosed": "로그인 터미널이 닫혔어요. 아래 터미널에서 다시 열어 주세요.",
487
+ "aiConnection.loginLinkSlow": "로그인 링크가 아직 나오지 않았어요. 아래 터미널에서 진행 상황을 확인해 주세요.",
488
+ "aiConnection.codeSent": "인증 코드를 보냈어요. 로그인이 끝나면 로그인 완료를 눌러 주세요.",
489
+ "aiConnection.showTerminal": "터미널 보기"
490
+ };
445
491
  const questionComposerKo = {
446
492
  "askUser.skip": "건너뛰기",
447
493
  "askUser.skipAnswer": "사용자가 이 질문의 답변을 건너뛰었습니다.",
@@ -481,8 +527,8 @@ const projectSettingsWebKo = {
481
527
  "projectSettings.webOnly": "웹 전용",
482
528
  "projectSettings.advancedWeb": "고급 실행 설정",
483
529
  "projectSettings.advancedHint": "멀티 AI도구(오케스트레이터)와 Sandbox 실행 격리는 웹 실행 환경의 설정입니다. 앱의 로컬 실행 권한·격리 설정과 별도로 관리합니다.",
484
- "projectSettings.cleanup": "빌드 산출물/캐시 정리",
485
- "projectSettings.cleanupHint": "클라우드 백업의 빌드 산출물과 캐시만 정리합니다. 작업PC의 파일은 삭제하지 않습니다.",
530
+ "projectSettings.cleanup": "빌드 작업물/캐시 정리",
531
+ "projectSettings.cleanupHint": "클라우드 백업의 빌드 작업물과 캐시만 정리합니다. 작업PC의 파일은 삭제하지 않습니다.",
486
532
  "projectSettings.cleanupPreview": "정리 대상 확인",
487
533
  "projectSettings.cleanupConfirm": "백업 파일 {count}개를 정리할까요?",
488
534
  "projectSettings.cleanupRun": "정리 실행",
@@ -663,6 +709,12 @@ const projectSettingsKo = {
663
709
  "projectSettings.diagnostics": "개발 정보"
664
710
  };
665
711
  const aiRecoveryKo = {
712
+ "aiRecovery.capacityTitle": "모델 혼잡 재시도",
713
+ "aiRecovery.capacitySummary": "지금은 요청이 많아요. 잠시 후 다시 시도해 주세요.",
714
+ "aiRecovery.capacityCountdown": "{seconds}초 후 다시 시도 ({attempt}/3)",
715
+ "aiRecovery.capacityCancel": "취소",
716
+ "aiRecovery.capacityExhausted": "자동 재시도를 마쳤어요. 잠시 후 다시 시도하거나 입력창에서 모델을 변경해주세요.",
717
+ "aiRecovery.capacityModelHint": "입력창에서 다른 모델로 변경할 수 있어요.",
666
718
  "aiRecovery.modelUnsupported": "현재 AI에서 사용할 수 없는 모델이에요. 입력창에서 모델을 변경한 뒤 다시 시도해주세요.",
667
719
  "aiRecovery.accessSummary": "이 계정으로 Claude를 사용할 수 없어요.",
668
720
  "aiRecovery.connectAccount": "다시 연결",
@@ -780,6 +832,8 @@ const managedClaudeKo = {
780
832
  "managedClaude.loading": "AI 연결 중…"
781
833
  };
782
834
  const workKo = {
835
+ "work.mediaChanged": "원본 파일이 변경됐습니다. 다시 열어주세요.",
836
+ "work.mediaUnsupported": "이 컴퓨터에서는 미디어 스트리밍을 지원하지 않습니다. 원본을 저장해 열어주세요.",
783
837
  "work.homeTitle": "어떤 작업을 도와드릴까요?",
784
838
  "work.new": "새 chat",
785
839
  "work.archived": "보관된 chat",
@@ -835,7 +889,8 @@ const workKo = {
835
889
  "work.sourceNotice.denied": "파일에 접근할 권한이 없어요. 접근 권한을 확인해 주세요.",
836
890
  "work.sourceNotice.unavailable": "파일을 확인하지 못했어요. 다시 시도해 주세요.",
837
891
  "work.saveCheck": "재저장 확인",
838
- "work.checkingFile": "확인 중…"
892
+ "work.checkingFile": "확인 중…",
893
+ "work.mediaViewerNeeded": "미디어 미리보기 확장을 설치하고 파일 읽기 권한을 허용해주세요."
839
894
  };
840
895
  const FILE_ACCESS_TRANSLATIONS_KO = {
841
896
  "fileAccess.retry": "다시 시도",
@@ -911,6 +966,8 @@ const organizationInstallRecoveryKo = {
911
966
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "파일을 실행하거나 버전을 확인하지 못했어요.",
912
967
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "도구 준비를 마치지 못했어요. 정확한 원인은 아직 확인되지 않았어요.",
913
968
  "organizationStart.toolDiagnostic.reason.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "설치된 cswap이 필요한 인증 기능을 지원하지 않아요.",
969
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_AUTH_REQUIRED": "회사 프록시가 로그인 인증을 요구해 연결하지 못했어요.",
970
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_BLOCKED": "회사 프록시가 다운로드나 패키지 서버 연결을 허용하지 않았어요.",
914
971
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "다시 시도하거나 설치 안내에 따라 준비한 뒤 다시 확인해 주세요.",
915
972
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "표시된 선행 도구를 먼저 준비한 뒤 다시 시도해 주세요.",
916
973
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "인터넷·회사 네트워크·프록시를 확인하고 다시 시도해 주세요.",
@@ -923,12 +980,55 @@ const organizationInstallRecoveryKo = {
923
980
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "네트워크를 확인한 뒤 다시 시도해 주세요. 반복되면 진단을 전달해 주세요.",
924
981
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "설치가 온전한지와 회사 정책이 실행을 허용하는지 확인해 주세요.",
925
982
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "다시 시도해 주세요. 반복되면 이 진단을 전달해 주세요.",
926
- "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "다시 시도로 Saycode가 사용하는 인증 도구를 설치해 주세요."
983
+ "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "다시 시도로 Saycode가 사용하는 인증 도구를 설치해 주세요.",
984
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_AUTH_REQUIRED": "이 진단을 회사 IT 담당자에게 전달해 다운로드 서버를 프록시 인증 예외로 허용해 달라고 요청해 주세요.",
985
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_BLOCKED": "이 진단을 회사 IT 담당자에게 전달해 다운로드 서버 접속 허용을 요청해 주세요."
986
+ };
987
+ const organizationAccountKeyKo = {
988
+ "organizationStart.accountKey.title": "대화 암호화 켜기",
989
+ "organizationStart.accountKey.body": "조직 AI를 이 컴퓨터에서 쓰려면 대화 암호화를 먼저 켜야 해요. 현재 비밀번호로 이 계정의 암호화 키를 만들어요.",
990
+ "organizationStart.accountKey.row": "대화 암호화",
991
+ "organizationStart.accountKey.passwordLabel": "현재 비밀번호",
992
+ "organizationStart.accountKey.checking": "비밀번호 확인 중…",
993
+ "organizationStart.accountKey.recoveryTitle": "복구 키를 보관해주세요",
994
+ "organizationStart.accountKey.recoveryNotice": "비밀번호를 잊었을 때 대화를 여는 방법이에요. 비밀번호와 복구 키를 모두 잃으면 암호화된 대화를 열 수 없어요. 이 화면을 닫으면 복구 키를 다시 볼 수 없어요.",
995
+ "organizationStart.accountKey.copy": "복사",
996
+ "organizationStart.accountKey.copied": "복구 키를 복사했어요.",
997
+ "organizationStart.accountKey.save": "파일로 저장",
998
+ "organizationStart.accountKey.saved": "복구 키를 저장했어요.",
999
+ "organizationStart.accountKey.confirmLabel": "복구 키 다시 입력",
1000
+ "organizationStart.accountKey.confirmMismatch": "입력한 복구 키가 위의 복구 키와 달라요.",
1001
+ "organizationStart.accountKey.enable": "대화 암호화 켜기",
1002
+ "organizationStart.accountKey.saving": "켜는 중…",
1003
+ "organizationStart.accountKey.renewBody": "확인 시간이 지나 비밀번호를 한 번 더 확인해야 해요. 보관한 복구 키는 그대로예요.",
1004
+ "organizationStart.accountKey.renew": "다시 확인",
1005
+ "organizationStart.accountKey.error.wrong-password": "현재 비밀번호가 일치하지 않아요.",
1006
+ "organizationStart.accountKey.error.rate-limited": "시도가 너무 많아요. 잠시 뒤 다시 시도해주세요.",
1007
+ "organizationStart.accountKey.error.already-enabled": "이 계정은 이미 대화 암호화가 켜져 있어요. 로그아웃한 뒤 다시 로그인해주세요.",
1008
+ "organizationStart.accountKey.error.signed-out": "로그인이 만료됐어요. 로그아웃한 뒤 다시 로그인해주세요.",
1009
+ "organizationStart.accountKey.error.expired": "확인 시간이 지났어요. 비밀번호를 다시 입력해주세요.",
1010
+ "organizationStart.accountKey.error.failed": "대화 암호화를 켜지 못했어요. 잠시 뒤 다시 시도해주세요.",
1011
+ "organizationStart.accountKey.error.recovery-mismatch": "입력한 복구 키가 위의 복구 키와 달라요.",
1012
+ "organizationStart.accountKey.lockedTitle": "다시 로그인해주세요",
1013
+ "organizationStart.accountKey.lockedBody": "이 계정은 대화 암호화가 켜져 있지만 이 컴퓨터에서는 아직 열리지 않았어요. 로그아웃한 뒤 다시 로그인하면 이어서 진행해요.",
1014
+ "organizationStart.accountKey.signInAgain": "다시 로그인",
1015
+ "organizationStart.diagnostic.reason.accountKey": "이 계정의 대화 암호화가 아직 켜져 있지 않아요.",
1016
+ "organizationStart.diagnostic.action.accountKey": "시작준비에서 대화 암호화를 켜주세요.",
1017
+ "organizationStart.diagnostic.reason.accountKeyLocked": "이 컴퓨터에서 계정 암호화 키가 아직 열리지 않았어요.",
1018
+ "organizationStart.diagnostic.action.accountKeyLocked": "로그아웃한 뒤 다시 로그인해주세요."
1019
+ };
1020
+ const organizationPcRegistrationKo = {
1021
+ "organizationStart.diagnostic.reason.pcNotAssigned": "이 PC가 지금 고른 조직에서 이 계정의 전용 PC로 배정돼 있지 않아요.",
1022
+ "organizationStart.diagnostic.action.pcNotAssigned": "고른 조직이 맞는지 확인하고, 맞으면 조직 관리자에게 이 PC를 이 계정의 전용 PC로 배정해 달라고 요청해주세요.",
1023
+ "organizationStart.diagnostic.reason.pcReregister": "이 PC의 예전 등록 정보를 이어서 쓸 수 없어요.",
1024
+ "organizationStart.diagnostic.action.pcReregister": "조직 관리자에게 이 PC를 새로 등록해 달라고 요청해주세요."
927
1025
  };
928
1026
  const organizationStartKo = {
929
1027
  "organizationStart.toolsDetails": "도구 준비 상세",
930
1028
  "organizationStart.toolsProgress": "도구 준비 상태",
931
1029
  ...organizationInstallRecoveryKo,
1030
+ ...organizationAccountKeyKo,
1031
+ ...organizationPcRegistrationKo,
932
1032
  "organizationStart.connectingTitle": "조직으로 연결 중",
933
1033
  "organizationStart.connectingBody": "{name}에서 작업할 준비를 하고 있어요.",
934
1034
  "organizationStart.welcome": "조직에서 바로 시작해요",
@@ -956,6 +1056,9 @@ const organizationStartKo = {
956
1056
  "organizationStart.aiGroupConflict": "조직의 AI 계정 설정을 확인해야 해요. 조직 관리자에게 알려주세요.",
957
1057
  "organizationStart.aiChecking": "AI 연결 중…",
958
1058
  "organizationStart.connectLater": "나중에 하기",
1059
+ "organizationStart.skip": "건너뛰기",
1060
+ "organizationStart.noAi.body": "{name}에 등록된 조직 AI 계정이 없어요. 건너뛰고 내 AI 계정으로 시작할 수 있어요.",
1061
+ "organizationStart.noAi.status": "조직 AI 계정 없음",
959
1062
  "organizationStart.existingConnection": "이 PC를 {name}에 연결하려면 다음을 눌러주세요.",
960
1063
  "organizationStart.failed": "시작준비",
961
1064
  "organizationStart.recovery.title": "앱 복구가 필요해요",
@@ -1062,7 +1165,32 @@ const organizationStartKo = {
1062
1165
  "organizationStart.diagnostic.reason.registrationChanged": "이 PC의 조직 등록이 바뀌었거나 삭제되어 이 계정으로 연결할 수 없어요.",
1063
1166
  "organizationStart.diagnostic.action.registrationChanged": "조직 관리자에게 조직 관리 → PC에서 이 PC의 등록을 확인해 달라고 요청한 뒤 다시 시도해주세요.",
1064
1167
  "organizationStart.diagnostic.reason.publicVerify": "공용 인증을 적용했지만 AI 인증 사용을 확인하지 못했어요.",
1065
- "organizationStart.diagnostic.action.publicVerify": "조직 관리자에게 최신 공용 인증 복구를 요청한 뒤 다시 연결해주세요."
1168
+ "organizationStart.diagnostic.action.publicVerify": "조직 관리자에게 최신 공용 인증 복구를 요청한 뒤 다시 연결해주세요.",
1169
+ "companyInvite.title": "받은 조직 초대",
1170
+ "companyInvite.checkFailed": "조직 초대 확인 필요",
1171
+ "companyInvite.body": "새 조직 초대가 있어요. 참여할 조직을 선택해주세요.",
1172
+ "companyInvite.onboardingBody": "받은 조직 초대를 먼저 확인해주세요.",
1173
+ "companyInvite.member": "멤버",
1174
+ "companyInvite.admin": "관리자",
1175
+ "companyInvite.accept": "수락",
1176
+ "companyInvite.decline": "거절",
1177
+ "companyInvite.later": "나중에",
1178
+ "companyInvite.dismiss": "조직 초대 닫기",
1179
+ "companyInvite.accepted": "조직에 참여했어요.",
1180
+ "companyInvite.declined": "조직 초대를 거절했어요.",
1181
+ "companyInvite.failed": "초대를 처리하지 못했어요. 잠시 후 다시 시도해주세요.",
1182
+ "companyInvite.reauth": "관리자 권한을 적용하려면 다시 로그인해야 해요.",
1183
+ "companyInvite.retry": "다시 확인",
1184
+ "companyInvite.securityRequired": "관리자 초대는 보안 인증이 필요해요. 웹에서 보안 설정과 인증을 확인한 뒤 다시 시도해주세요.",
1185
+ "companyInvite.expiry": "{date}까지",
1186
+ "companyInvite.declineConfirm": "거절 확인",
1187
+ "companyInvite.declineBody": "{name}의 초대를 거절할까요? 참여하려면 새 초대가 필요해요.",
1188
+ "companyInvite.cancel": "취소",
1189
+ "companyInvite.loading": "초대 확인 중…",
1190
+ "companyInvite.acceptLabel": "{name} 초대 수락",
1191
+ "companyInvite.declineLabel": "{name} 초대 거절",
1192
+ "companyInvite.declineConfirmLabel": "{name} 초대 거절 확인",
1193
+ "companyInvite.refreshFailed": "초대 목록을 확인하지 못했어요. 다시 확인해주세요."
1066
1194
  };
1067
1195
  const usageEntryKo = {
1068
1196
  ...selfHostConnectKo,
@@ -1202,7 +1330,7 @@ const usageEntryKo = {
1202
1330
  "localTools.prerequisitesBody": "uv·Git·Node.js(npm 포함)의 설치 상태를 확인합니다. 설치 후 다시 확인하세요.",
1203
1331
  "localTools.prerequisitesTitle": "기본 개발 도구",
1204
1332
  "onboarding.preparationComplete": "필수 설정 완료",
1205
- "onboarding.additionalStepsBody": "Saycode 지시, AI 계정, 알림, 컴퓨터·브라우저 제어는 선택 단계입니다. 나중에 설정해도 바로 시작할 수 있습니다.",
1333
+ "onboarding.additionalStepsBody": "Saycode 지시, AI 계정, 알림은 선택 단계입니다. 나중에 설정해도 바로 시작할 수 있습니다.",
1206
1334
  "onboarding.additionalStep": "추가 단계",
1207
1335
  "onboarding.requiredSettings": "필수 설정",
1208
1336
  "onboarding.additionalSettings": "추가 설정 — 선택",
@@ -1285,6 +1413,11 @@ const projectRelocationKO = {
1285
1413
  "sessionCreation.workspaceNotSet": "이 머신의 워크스페이스 경로가 설정되지 않았습니다. 머신 워크스페이스를 먼저 설정해주세요."
1286
1414
  };
1287
1415
  const chatSchedulesKo = {
1416
+ "chatSchedules.chatTitle": "이 대화의 예약",
1417
+ "chatSchedules.sharedHint": "예약 메뉴에서도 같은 예약을 확인하고 관리할 수 있어요.",
1418
+ "chatSchedules.allSchedules": "전체 예약 보기",
1419
+ "chatSchedules.advanced": "고급 자동화 설정",
1420
+ "chatSchedules.advancedHint": "AI 선택·조건 실행·스크립트 자동화를 설정해요. 위 예약과 같은 AI 예약을 관리하며, 스크립트 자동화는 별도 실행 유형이에요.",
1288
1421
  "chatSchedules.allReplies": "회차별 답변",
1289
1422
  "chatSchedules.allRepliesHint": "최근 실행 기록의 답변을 시간순으로 보여줘요. 최근 20회까지 확인할 수 있어요.",
1290
1423
  "chatSchedules.replyLoadError": "답변을 불러오지 못했어요.",
@@ -1730,7 +1863,12 @@ const machineUsageKo = {
1730
1863
  "machineUsage.autoSwitched": "{provider} 계정을 자동 전환했습니다: {from} → {to}",
1731
1864
  "machineUsage.pinned": "고정됨",
1732
1865
  "machineUsage.codexAccounts": "Codex 계정",
1733
- "machineUsage.usageStale": "사용량 갱신 중"
1866
+ "machineUsage.usageStale": "사용량 갱신 중",
1867
+ "machineUsage.autoRotation": "자동순환계정",
1868
+ "machineUsage.autoRotationOn": "한도에 가까워지면 다른 계정으로 자동 전환해요.",
1869
+ "machineUsage.autoRotationOff": "선택한 계정을 계속 사용해요.",
1870
+ "machineUsage.autoRotationPaused": "계정을 직접 선택해 자동 순환을 껐어요.",
1871
+ "machineUsage.autoRotationFailed": "자동 순환을 바꾸지 못했어요. 다시 시도해주세요."
1734
1872
  };
1735
1873
  const machineResourceMetricsKo = {
1736
1874
  "machineResourceMetrics.title": "작업PC 리소스",
@@ -2145,7 +2283,7 @@ const outboundWebhookKo = {
2145
2283
  };
2146
2284
  const messengerChannelsKo = {
2147
2285
  "settings.messengerChannels.title": "메신저 채널",
2148
- "settings.messengerChannels.privacyNotice": "연결된 메신저에는 요청의 상태와 최종 답변만 전송됩니다 — raw tool output·과거 기록·첨부는 전송되지 않습니다. 메신저 구간은 Saycode의 종단간 암호화 범위 밖입니다.",
2286
+ "settings.messengerChannels.privacyNotice": "연결된 메신저에는 요청의 상태와 최종 답변이 전송되고, Slack에서는 그 답변에서 에이전트가 지정한 프로젝트 폴더 안 파일도 함께 전송됩니다. raw tool output·과거 기록은 전송되지 않습니다. 메신저 구간은 Saycode의 종단간 암호화 범위 밖입니다.",
2149
2287
  "settings.messengerChannels.alwaysOnTitle": "앱 종료 후에도 연결 유지",
2150
2288
  "settings.messengerChannels.alwaysOnHelp": "Saycode 앱을 종료해도 메시지를 받고 작업을 이어갑니다. 컴퓨터가 켜져 있고 인터넷에 연결되어 있어야 합니다.",
2151
2289
  "settings.messengerChannels.alwaysOnBlocked": "사용할 수 없습니다: 이 기기에서 봇 자격 증명을 안전하게 저장할 수 없어, Desktop 종료 후에는 아무것도 수신할 수 없습니다.",
@@ -2303,7 +2441,7 @@ const messengerQuickSetupKo = {
2303
2441
  "settings.messengerChannels.quickSlack.openInSlack": "Slack에서 열기",
2304
2442
  "settings.messengerChannels.quickSlack.waiting": "Slack에서 메시지를 보내면 여기에 연결 요청이 떠요. 허용하면 끝이에요.",
2305
2443
  "settings.messengerChannels.quickSlack.pairedWith": "{name}님의 Slack과 연결됨",
2306
- "settings.messengerChannels.quickSlack.connectedHelp": "코드를 보낸 DM에서 봇에게 그냥 말을 걸면 돼요. 명령어는 봇을 멘션한 뒤 /help 로 볼 수 있어요.",
2444
+ "settings.messengerChannels.quickSlack.connectedHelp": "코드를 보낸 DM에서 봇에게 그냥 말을 걸면 돼요. 봇을 초대한 채널에서 멘션하면 그 글의 스레드로 답해요(답변은 채널 멤버에게 보여요). 명령어는 봇을 멘션한 뒤 /help 로 볼 수 있어요.",
2307
2445
  "settings.messengerChannels.quickDiscord.title": "Discord 연결",
2308
2446
  "settings.messengerChannels.quickDiscord.subtitle": "Discord에서 Saycode에게 바로 일을 맡길 수 있어요. 두 단계면 끝나요.",
2309
2447
  "settings.messengerChannels.quickDiscord.installTitle": "Discord 기능이 아직 꺼져 있어요",
@@ -2608,6 +2746,9 @@ const extensionPanelKo = {
2608
2746
  const extensionInstallKo = {
2609
2747
  "extensions.installOfficial": "확장 설치",
2610
2748
  "extensions.installNamedOfficial": "{name} 설치",
2749
+ "extensions.officialUpdate": "{version}으로 공식 업데이트",
2750
+ "extensions.updateSaved": "새 버전은 설치됐지만 마무리 확인을 하지 못했어요. 확장 상태를 다시 확인하세요.",
2751
+ "extensions.projectToolHint": "프로젝트 대화의 오른쪽 작업 패널 더보기(⋮) 메뉴에서 엽니다: {tools}",
2611
2752
  "extensions.officialName.artifactPublisher": "공개 링크 확장",
2612
2753
  "extensions.officialName.pluginManager": "플러그인 관리자 확장",
2613
2754
  "extensions.officialName.projectTemplates": "프로젝트 템플릿 확장",
@@ -2636,25 +2777,26 @@ const extensionInstallKo = {
2636
2777
  "artifactPublication.error.network": "서버에 연결할 수 없습니다. 네트워크 상태를 확인해 주세요.",
2637
2778
  "artifactPublication.error.unauthenticated": "로그인 세션이 만료되었습니다. 다시 로그인한 뒤 시도해 주세요.",
2638
2779
  "artifactPublication.error.notFound": "파일을 찾을 수 없습니다. 파일이 저장되었는지 확인해 주세요.",
2780
+ "artifactPublication.error.notFoundInFolder": "작업 폴더 {folder}에서 {file} 파일을 찾을 수 없습니다. 다른 작업 폴더(worktree)에서 만든 파일이면 이 폴더로 옮긴 뒤 다시 시도해 주세요.",
2639
2781
  "artifactPublication.error.tooLarge": "파일이 너무 큽니다. 공개할 HTML 파일은 10MB 이하여야 합니다.",
2640
2782
  "artifactPublication.error.rateLimited": "공개 링크 요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.",
2641
2783
  "artifactPublication.error.rejected": "서버가 이 파일의 공개 요청을 거부했습니다.",
2642
2784
  "artifactPublication.error.serverError": "서버 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
2643
2785
  "artifactPublication.error.extensionUnavailable": "확장이 비활성화되었거나 공개 권한이 없습니다. 설정에서 Artifact Publisher를 확인해 주세요.",
2644
- "artifactPublication.error.invalidResponse": "서버 응답을 확인할 수 없어 중단했습니다."
2786
+ "artifactPublication.error.invalidResponse": "서버 응답을 확인할 수 없어 중단했습니다.",
2787
+ "extensions.permission.filePreviewsRead": "앱에서 연 파일의 미리보기 내용을 읽을 수 있습니다. 원본 경로와 인증 정보는 Saycode Core가 관리합니다."
2645
2788
  };
2646
2789
  const artifactLibraryTranslations$3 = {
2647
- "artifactLibrary.title": "산출물 모아보기",
2790
+ "artifactLibrary.title": "작업물 모아보기",
2648
2791
  "artifactLibrary.scope": "범위",
2649
2792
  "artifactLibrary.conversation": "이 대화",
2650
2793
  "artifactLibrary.project": "이 프로젝트",
2651
- "artifactLibrary.all": "전체 프로젝트",
2652
2794
  "artifactLibrary.search": "파일·프로젝트·대화·경로 검색",
2653
- "artifactLibrary.hint": "대화에서 만든 문서와 미리보기를 모았습니다. 원본 파일을 엽니다.",
2654
- "artifactLibrary.loading": "산출물을 찾고 있습니다…",
2795
+ "artifactLibrary.loading": "작업물을 찾고 있습니다…",
2655
2796
  "artifactLibrary.error": "일부 대화를 조회하지 못했습니다. 다시 시도해 주세요.",
2656
- "artifactLibrary.empty": "아직 산출물이 없습니다.",
2797
+ "artifactLibrary.empty": "아직 작업물이 없습니다.",
2657
2798
  "artifactLibrary.noMatches": "검색 결과가 없습니다.",
2799
+ "artifactLibrary.readUnsupported": "이 PC에서는 작업물 원본 읽기를 지원하지 않습니다. 현재 macOS 작업PC에서 지원됩니다.",
2658
2800
  "artifactLibrary.retry": "다시 시도"
2659
2801
  };
2660
2802
  const personalChatsLandingKo = {
@@ -3518,9 +3660,15 @@ const sidebarUpdateKo = {
3518
3660
  "sidebarUpdate.completedBody": "Saycode v{version} 업데이트가 완료됐어요.",
3519
3661
  "sidebarUpdate.installing": "업데이트 적용 중…",
3520
3662
  "sidebarUpdate.update": "업데이트",
3521
- "sidebarUpdate.restartNotice": "재시작 전에 대화 상태를 저장합니다.",
3663
+ "sidebarUpdate.restartNotice": "재시작 전에 대화 상태를 저장합니다. 설치가 끝나면 Saycode가 자동으로 다시 열려요.",
3522
3664
  "sidebarUpdate.minimize": "최소화",
3523
- "sidebarUpdate.releaseNotes": "Release notes"
3665
+ "sidebarUpdate.releaseNotes": "Release notes",
3666
+ "sidebarUpdate.relaunchWaitTitle": "업데이트를 설치하고 있어요",
3667
+ "sidebarUpdate.relaunchWaitBody": "설치가 끝나면 Saycode v{version}이 자동으로 열려요. 이 창을 닫아도 설치는 계속됩니다.",
3668
+ "sidebarUpdate.relaunchTimeoutTitle": "업데이트 설치를 마치지 못했어요",
3669
+ "sidebarUpdate.relaunchTimeoutBody": "현재 버전(v{version})으로 열 수 있어요. 앱에서 업데이트를 다시 받을 수 있습니다.",
3670
+ "sidebarUpdate.relaunchOpenCurrent": "현재 버전으로 열기",
3671
+ "sidebarUpdate.relaunchQuit": "종료"
3524
3672
  };
3525
3673
  const globalPauseKo = {
3526
3674
  "settings.globalPause.enable": "자동 작업 일시정지",
@@ -4248,8 +4396,8 @@ const designGuideChatKo = {
4248
4396
  "designGuide.chat.card.question": "사용자 질문",
4249
4397
  "designGuide.chat.card.activity": "진행 상태 행",
4250
4398
  "designGuide.chat.card.error": "답변 오류",
4251
- "designGuide.chat.card.preview": "미리보기, 산출물 링크",
4252
- "designGuide.chat.card.artifact": "Chat 산출물",
4399
+ "designGuide.chat.card.preview": "미리보기, 작업물 링크",
4400
+ "designGuide.chat.card.artifact": "Chat 작업물",
4253
4401
  "designGuide.chat.card.checkpoint": "파일 복원지점",
4254
4402
  "designGuide.chat.card.subagent": "보조 AI도구",
4255
4403
  "designGuide.chat.card.queue": "입력 대기열",
@@ -4845,7 +4993,6 @@ const onboardingKo = {
4845
4993
  "onboarding.page.addProject": "첫 프로젝트 추가",
4846
4994
  "onboarding.page.configured": "설정됨",
4847
4995
  "onboarding.page.optional": "선택",
4848
- "onboarding.page.computerReveal": "내부 테스트: 컴퓨터·브라우저 제어 표시",
4849
4996
  "onboarding.page.testNotification": "테스트 알림 보내기",
4850
4997
  "onboarding.page.testNotificationSending": "테스트 알림 보내는 중…",
4851
4998
  "onboarding.page.testNotificationSuccess": "테스트 알림을 보냈습니다.",
@@ -5020,6 +5167,7 @@ const ko = {
5020
5167
  "aiModelPolicy.machineChanged": "선택한 PC에 연결할 수 없어 대화 시작을 멈췄어요. PC를 확인한 뒤 다시 보내 주세요.",
5021
5168
  "aiModelPolicy.policyChanged": "허용 모델 정책이 바뀌어 대화 시작을 멈췄어요. 질문과 첨부를 확인한 뒤 다시 보내 주세요.",
5022
5169
  "aiModelPolicy.noModels": "이 컴퓨터에서 선택할 수 있는 허용 모델이 없어요.",
5170
+ ...personalAiKo,
5023
5171
  ...conversationTabsKo,
5024
5172
  ...questionComposerKo,
5025
5173
  ...aiRecoveryKo,
@@ -5980,7 +6128,7 @@ const ko = {
5980
6128
  "deployModal.pbAdmin": "PocketBase 관리자 계정",
5981
6129
  "deployModal.email": "이메일:",
5982
6130
  "deployModal.credentialsFile": "자격증명 파일:",
5983
- "deployModal.pbPasswordNote": "비밀번호는 배포 산출물의 자격증명 파일에 저장됩니다. 안전한 곳에 보관하세요.",
6131
+ "deployModal.pbPasswordNote": "비밀번호는 배포 작업물의 자격증명 파일에 저장됩니다. 안전한 곳에 보관하세요.",
5984
6132
  // Project settings panel
5985
6133
  "repositorySettings.title": "코드 저장소",
5986
6134
  "repositorySettings.statusSaved": "저장됨",
@@ -7354,6 +7502,9 @@ const ko = {
7354
7502
  "projectGitView.branchDelete": "Branch 삭제",
7355
7503
  // Remote terminal + API client (non-hook)
7356
7504
  "terminal.connectTimeout": "터미널 연결 시간이 초과되었습니다.",
7505
+ "terminal.strictMachineUnsupported": "보안 강화(strict) 작업PC에는 아직 원격 터미널을 열 수 없습니다. 작업PC에서 직접 터미널을 사용하세요.",
7506
+ "terminal.machineControlUnknown": "이 작업PC의 CLI가 보안 모드를 알려주지 않아 원격 터미널을 열 수 없습니다. 작업PC에서 happy CLI를 최신 버전으로 업데이트하세요.",
7507
+ "terminal.machineKeyUnavailable": "이 작업PC의 키를 열 수 없어 원격 터미널을 열지 않았습니다. 계정 키 잠금을 해제한 뒤 다시 시도하세요.",
7357
7508
  "terminal.connectFailed": "터미널 연결에 실패했습니다.",
7358
7509
  "terminal.cwdPrepFailed": "원격 터미널 작업 디렉토리를 준비하지 못했습니다.",
7359
7510
  "terminal.restoredOutputDivider": "이전 세션 출력",
@@ -7685,8 +7836,8 @@ const ko = {
7685
7836
  "fileDiscovery.results": "검색 결과",
7686
7837
  "fileDiscovery.empty": "일치하는 내용 없음",
7687
7838
  "fileDiscovery.lineChanged": "파일이 변경되어 이동 가능한 가장 가까운 행을 표시합니다.",
7688
- "fileDiscovery.newArtifacts": "새 산출물",
7689
- "fileDiscovery.newArtifact": "새 산출물"
7839
+ "fileDiscovery.newArtifacts": "새 작업물",
7840
+ "fileDiscovery.newArtifact": "새 작업물"
7690
7841
  };
7691
7842
  const CATALOGS = { ko };
7692
7843
  function registerCatalog(language, catalog2) {
@@ -8325,10 +8476,10 @@ In a chat
8325
8476
  /model · /effort · /voice [path] · /detach · /exit live chat commands
8326
8477
 
8327
8478
  Leaving never stops a remote session — it keeps running on the machine.`;
8328
- const record$i = (value2) => !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8479
+ const record$j = (value2) => !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8329
8480
  const ids = (value2) => Array.isArray(value2) && value2.length <= 1e3 && value2.every((id2) => typeof id2 === "string" && id2.trim().length > 0);
8330
8481
  function parseMachineSharing(value2) {
8331
- if (!record$i(value2) || !record$i(value2.machine)) throw new Error("Invalid machine sharing response");
8482
+ if (!record$j(value2) || !record$j(value2.machine)) throw new Error("Invalid machine sharing response");
8332
8483
  const m = value2.machine;
8333
8484
  if (typeof value2.revision !== "string" || !value2.revision || !["canShare", "canMakeDedicated", "canManageAccess"].every((key) => typeof value2[key] === "boolean") || !["machineId", "ownerId", "companyId", "infraMode"].every((key) => typeof m[key] === "string" && !!m[key]) || !["dedicated", "shared"].includes(String(m.type)) || !ids(m.accessList) || m.accessList.length === 0 || m.accessList.includes("*") && m.accessList.length !== 1 || m.assignedUserId != null && typeof m.assignedUserId !== "string" || value2.missingUserIds !== void 0 && !ids(value2.missingUserIds)) throw new Error("Invalid machine sharing response");
8334
8485
  return {
@@ -8593,7 +8744,7 @@ function parseCompanyMemberDirectoryPage(value2) {
8593
8744
  nextCursor: rawCursor
8594
8745
  };
8595
8746
  }
8596
- function record$h(value2) {
8747
+ function record$i(value2) {
8597
8748
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid team collaboration response");
8598
8749
  return value2;
8599
8750
  }
@@ -8606,12 +8757,12 @@ function choice(value2, values) {
8606
8757
  return value2;
8607
8758
  }
8608
8759
  function parseCollaboration(value2) {
8609
- const row2 = record$h(value2);
8760
+ const row2 = record$i(value2);
8610
8761
  if (!Array.isArray(row2.teamIds) || row2.teamIds.length > 1e3 || !Array.isArray(row2.bindings) || row2.bindings.length > 1e3) throw new Error("Invalid team collaboration list");
8611
8762
  return {
8612
8763
  teamIds: row2.teamIds.map(identifier$4),
8613
8764
  bindings: row2.bindings.map((value22) => {
8614
- const binding = record$h(value22);
8765
+ const binding = record$i(value22);
8615
8766
  return {
8616
8767
  accountId: identifier$4(binding.accountId),
8617
8768
  membershipOrigin: choice(binding.membershipOrigin, ["unresolved", "team", "preexisting"]),
@@ -8623,8 +8774,8 @@ function parseCollaboration(value2) {
8623
8774
  };
8624
8775
  }
8625
8776
  function parseMutation(value2) {
8626
- const row2 = record$h(value2);
8627
- const sync = row2.sync === null ? null : record$h(row2.sync);
8777
+ const row2 = record$i(value2);
8778
+ const sync = row2.sync === null ? null : record$i(row2.sync);
8628
8779
  if (sync && [sync.synced, sync.failed, sync.rejected].some((value22) => !Number.isSafeInteger(value22) || Number(value22) < 0)) throw new Error("Invalid team synchronization counts");
8629
8780
  return { collaboration: parseCollaboration(row2.collaboration), sync, ...parseProjectMemberRemovalResult(row2) };
8630
8781
  }
@@ -8632,7 +8783,7 @@ function createProjectTeamCollaborationApi(request2) {
8632
8783
  const path2 = (id2) => `/api/projects/${encodeURIComponent(id2)}/collaboration/teams`;
8633
8784
  return {
8634
8785
  async getProjectTeamCollaboration(id2) {
8635
- return parseCollaboration(record$h(await request2(path2(id2))).collaboration);
8786
+ return parseCollaboration(record$i(await request2(path2(id2))).collaboration);
8636
8787
  },
8637
8788
  async updateProjectTeamCollaboration(id2, teamIds) {
8638
8789
  if (teamIds.length > 1e3) throw new Error("Too many collaboration teams");
@@ -8646,6 +8797,8 @@ function createProjectTeamCollaborationApi(request2) {
8646
8797
  }
8647
8798
  const AI_GROUP_ERROR_KINDS = [
8648
8799
  "AI_GROUP_ACCOUNT_IDENTITY_UNAVAILABLE",
8800
+ "AI_GROUP_ACCOUNT_KEY_LOCKED",
8801
+ "AI_GROUP_ACCOUNT_KEY_REQUIRED",
8649
8802
  "AI_GROUP_ACCOUNT_MISSING",
8650
8803
  "AI_GROUP_APPLY_BUSY",
8651
8804
  "AI_GROUP_APPLY_FAILED",
@@ -8671,6 +8824,8 @@ const AI_GROUP_ERROR_KINDS = [
8671
8824
  "AI_GROUP_ORGANIZATION_KEY_REQUIRED",
8672
8825
  "AI_GROUP_PAYLOAD_CONFLICT",
8673
8826
  "AI_GROUP_PAYLOAD_TOO_LARGE",
8827
+ "AI_GROUP_PC_NOT_ASSIGNED",
8828
+ "AI_GROUP_PC_REREGISTER_REQUIRED",
8674
8829
  "AI_GROUP_PREPARE_FAILED",
8675
8830
  "AI_GROUP_RECEIPT_INVALID",
8676
8831
  "AI_GROUP_RECEIPT_MISMATCH",
@@ -8701,11 +8856,11 @@ function parseAiGroupErrorKind(value2) {
8701
8856
  return typeof value2 === "string" && AI_GROUP_ERROR_KINDS.includes(value2) ? value2 : "UNKNOWN_ERROR";
8702
8857
  }
8703
8858
  const MANAGED_ACCOUNT_ID = /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i;
8704
- function record$g(value2) {
8859
+ function record$h(value2) {
8705
8860
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid AI group metadata");
8706
8861
  return value2;
8707
8862
  }
8708
- function text$8(value2, max = 254) {
8863
+ function text$9(value2, max = 254) {
8709
8864
  if (typeof value2 !== "string" || !value2 || value2.length > max) throw new Error("Invalid AI group metadata");
8710
8865
  return value2;
8711
8866
  }
@@ -8730,31 +8885,31 @@ function receiptMetadata(result2) {
8730
8885
  ...result2.assignmentGeneration == null ? {} : { assignmentGeneration: positiveInteger(result2.assignmentGeneration) },
8731
8886
  ...result2.fingerprint == null ? {} : { fingerprint: digest$2(result2.fingerprint) },
8732
8887
  ...result2.appliedCredentials == null ? {} : { appliedCredentials: list$1(result2.appliedCredentials, 500).map((entry) => {
8733
- const credential = record$g(entry), managedAccountId = text$8(credential.managedAccountId, 80);
8888
+ const credential = record$h(entry), managedAccountId = text$9(credential.managedAccountId, 80);
8734
8889
  if (!MANAGED_ACCOUNT_ID.test(managedAccountId)) throw new Error("Invalid AI group metadata");
8735
8890
  return { managedAccountId, credentialGeneration: positiveInteger(credential.credentialGeneration) };
8736
8891
  }) }
8737
8892
  };
8738
8893
  }
8739
8894
  function parseAiGroupAssignments(value2, companyId) {
8740
- const body = record$g(value2);
8895
+ const body = record$h(value2);
8741
8896
  if (body.version !== 1 || body.companyId !== companyId) throw new Error("Invalid AI group scope");
8742
8897
  return { version: 1, companyId, groups: list$1(body.groups, 1e3).map((value22) => {
8743
- const group = record$g(value22);
8898
+ const group = record$h(value22);
8744
8899
  return {
8745
- credentialGroupId: text$8(group.credentialGroupId, 128),
8746
- credentialGroupName: text$8(group.credentialGroupName, 80),
8747
- userGroupName: text$8(group.userGroupName, 80),
8900
+ credentialGroupId: text$9(group.credentialGroupId, 128),
8901
+ credentialGroupName: text$9(group.credentialGroupName, 80),
8902
+ userGroupName: text$9(group.userGroupName, 80),
8748
8903
  accounts: list$1(group.accounts, 500).map((value3) => {
8749
- const account = record$g(value3), identity = text$8(account.identity);
8904
+ const account = record$h(value3), identity = text$9(account.identity);
8750
8905
  if (!/^[a-f0-9]{64}$/.test(identity) || !Number.isSafeInteger(account.bundleVersion) || Number(account.bundleVersion) < 1 || account.kind !== "oauth" && account.kind !== "api_key") throw new Error("Invalid AI group account");
8751
8906
  const managed = account.credentialType === "setup_token";
8752
8907
  if (managed && (account.provider !== "claude" || account.kind !== "oauth" || typeof account.managedAccountId !== "string" || !MANAGED_ACCOUNT_ID.test(account.managedAccountId) || !Number.isSafeInteger(account.credentialGeneration) || Number(account.credentialGeneration) < 1)) throw new Error("Invalid AI group managed account");
8753
8908
  return {
8754
- bundleId: text$8(account.bundleId, 128),
8909
+ bundleId: text$9(account.bundleId, 128),
8755
8910
  identity,
8756
8911
  provider: provider$1(account.provider),
8757
- label: text$8(account.label),
8912
+ label: text$9(account.label),
8758
8913
  kind: account.kind,
8759
8914
  bundleVersion: Number(account.bundleVersion),
8760
8915
  ...managed ? { credentialType: "setup_token", managedAccountId: String(account.managedAccountId), credentialGeneration: Number(account.credentialGeneration), authState: "unverified" } : {}
@@ -8762,11 +8917,11 @@ function parseAiGroupAssignments(value2, companyId) {
8762
8917
  })
8763
8918
  };
8764
8919
  }), results: list$1(body.results, 5e3).map((value22) => {
8765
- const result2 = record$g(value22), status = result2.status;
8920
+ const result2 = record$h(value22), status = result2.status;
8766
8921
  if (status !== "pending" && status !== "applied" && status !== "revoked" && status !== "failed" && status !== "action_required") throw new Error("Invalid AI group result");
8767
8922
  const errorKind = parseAiGroupErrorKind(result2.errorKind);
8768
8923
  return {
8769
- machineId: text$8(result2.machineId, 128),
8924
+ machineId: text$9(result2.machineId, 128),
8770
8925
  provider: provider$1(result2.provider),
8771
8926
  status,
8772
8927
  ...errorKind ? { errorKind } : {},
@@ -8837,8 +8992,10 @@ function parseCollectorAssignment(value2) {
8837
8992
  const machineId = v.machineId === null ? null : identifier$3(v.machineId);
8838
8993
  const credentialGeneration = int$1(v.credentialGeneration, 0), policyRevision = int$1(v.policyRevision, 0);
8839
8994
  if (v.enabled && (!machineId || !credentialGeneration || !policyRevision) || v.probeAllowed && (!v.enabled || v.status !== "ready")) throw new Error("CLAUDE_COLLECTOR_INVALID_RESPONSE");
8995
+ if (v.principalType !== void 0 && v.principalType !== "user" && v.principalType !== "machine") throw new Error("CLAUDE_COLLECTOR_INVALID_RESPONSE");
8840
8996
  return {
8841
8997
  version: 1,
8998
+ principalType: v.principalType ?? "user",
8842
8999
  enabled: v.enabled,
8843
9000
  probeAllowed: v.probeAllowed,
8844
9001
  status: v.status,
@@ -8890,26 +9047,26 @@ function createClaudeCollectorApi(transport) {
8890
9047
  }
8891
9048
  };
8892
9049
  }
8893
- const record$f = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
8894
- const timestamp = (value2) => Number.isSafeInteger(value2) && value2 >= 0;
9050
+ const record$g = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
9051
+ const timestamp$1 = (value2) => Number.isSafeInteger(value2) && value2 >= 0;
8895
9052
  function parseWindow(value2) {
8896
- const row2 = record$f(value2);
8897
- if (!row2 || typeof row2.id !== "string" || !row2.id || !timestamp(row2.openedAt) || !timestamp(row2.endsAt) || !timestamp(row2.waitMinutes)) return null;
9053
+ const row2 = record$g(value2);
9054
+ if (!row2 || typeof row2.id !== "string" || !row2.id || !timestamp$1(row2.openedAt) || !timestamp$1(row2.endsAt) || !timestamp$1(row2.waitMinutes)) return null;
8898
9055
  return { id: row2.id, openedAt: row2.openedAt, endsAt: row2.endsAt, waitMinutes: row2.waitMinutes };
8899
9056
  }
8900
9057
  function parseMe(value2) {
8901
- const row2 = record$f(value2);
9058
+ const row2 = record$g(value2);
8902
9059
  if (!row2) return null;
8903
9060
  const { hasLatestGrant, pending: pending2, autoEligible, autoAt, held } = row2;
8904
9061
  if (typeof hasLatestGrant !== "boolean" || typeof pending2 !== "boolean" || typeof autoEligible !== "boolean" || typeof held !== "boolean") return null;
8905
- if (autoAt !== null && !timestamp(autoAt)) return null;
9062
+ if (autoAt !== null && !timestamp$1(autoAt)) return null;
8906
9063
  return { hasLatestGrant, pending: pending2, autoEligible, autoAt, held };
8907
9064
  }
8908
9065
  function companyKeyAutoWindowHeaders(options) {
8909
9066
  return options?.distributor ? { "X-Saycode-Auto-Grant-Client": "desktop" } : {};
8910
9067
  }
8911
9068
  function parseCompanyKeyAutoWindow(raw) {
8912
- const body = record$f(raw);
9069
+ const body = record$g(raw);
8913
9070
  return { window: parseWindow(body?.window), me: parseMe(body?.me) };
8914
9071
  }
8915
9072
  function companyKeyGrantFailure(error) {
@@ -8919,7 +9076,7 @@ function companyKeyGrantFailure(error) {
8919
9076
  const body = "body" in error ? error.body : void 0;
8920
9077
  if (typeof body === "string") {
8921
9078
  try {
8922
- const parsed2 = record$f(JSON.parse(body));
9079
+ const parsed2 = record$g(JSON.parse(body));
8923
9080
  if (typeof parsed2?.error === "string") reason = parsed2.error;
8924
9081
  } catch {
8925
9082
  }
@@ -11590,32 +11747,32 @@ function invalidateRendererQueries(kind, scope2) {
11590
11747
  }
11591
11748
  const ROUTING_POLICY = "org-shared-difficulty-routing.v1";
11592
11749
  const ROUTING_AGENTS = ["claude", "codex"];
11593
- function record$e(value2) {
11750
+ function record$f(value2) {
11594
11751
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
11595
11752
  }
11596
11753
  function id$2(value2) {
11597
11754
  return typeof value2 === "string" && value2.length > 0 && value2.length <= 256;
11598
11755
  }
11599
11756
  function parseRoutingResult(value2) {
11600
- const r = record$e(value2);
11601
- const baseRoute = record$e(r?.baseRoute);
11602
- const previousApplied = record$e(r?.previousApplied);
11757
+ const r = record$f(value2);
11758
+ const baseRoute = record$f(r?.baseRoute);
11759
+ const previousApplied = record$f(r?.previousApplied);
11603
11760
  if (!r || r.version !== 1 || r.mode !== "auto" || r.policyVersion !== ROUTING_POLICY || !id$2(r.clientRequestId) || !id$2(r.model) || !(r.effort === null || id$2(r.effort)) || !(r.policyRevision === null || Number.isSafeInteger(r.policyRevision) && r.policyRevision >= 0) || !["trivial", "routine", "hard", "escalated"].includes(String(r.difficulty)) || !["p1-local", "p2-org-shared", "p2-local", "fallback-p1", "manual-legacy"].includes(String(r.classifierSource)) || r.stage !== void 0 && !["selected", "queued", "applied", "failed", "cancelled", "unknown"].includes(String(r.stage)) || r.revision !== void 0 && (!Number.isSafeInteger(r.revision) || r.revision < 0) || r.evidence !== void 0 && !["engine-applied", "legacy-selection", "unknown"].includes(String(r.evidence)) || r.executionId !== void 0 && !id$2(r.executionId) || r.clientRequestIds !== void 0 && (!Array.isArray(r.clientRequestIds) || r.clientRequestIds.length > 256 || !r.clientRequestIds.every(id$2)) || r.candidateDifficulty !== void 0 && !["trivial", "routine", "hard", "escalated"].includes(String(r.candidateDifficulty)) || r.baseRoute !== void 0 && (!baseRoute || !["trivial", "routine", "hard", "escalated"].includes(String(baseRoute.difficulty)) || !id$2(baseRoute.model) || !(baseRoute.effort === null || id$2(baseRoute.effort))) || r.temporaryEscalation !== void 0 && typeof r.temporaryEscalation !== "boolean" || r.decisionReasons !== void 0 && (!Array.isArray(r.decisionReasons) || r.decisionReasons.length > 16 || !r.decisionReasons.every((reason) => typeof reason === "string" && reason.length > 0 && reason.length <= 64)) || r.providerConfirmedModel !== void 0 && !id$2(r.providerConfirmedModel) || r.previousApplied !== void 0 && (!previousApplied || !id$2(previousApplied.model) || !(previousApplied.effort === null || id$2(previousApplied.effort)) || !["trivial", "routine", "hard", "escalated"].includes(String(previousApplied.difficulty)) || !["auto", "local-auto-bootstrap", "manual"].includes(String(previousApplied.kind)))) return null;
11604
11761
  return r;
11605
11762
  }
11606
11763
  function parseRoutingPolicy(value2) {
11607
- const p = record$e(value2);
11764
+ const p = record$f(value2);
11608
11765
  if (!p || p.schemaVersion !== 1 || typeof p.enabled !== "boolean" || p.policyVersion !== ROUTING_POLICY || !Number.isSafeInteger(p.revision) || p.revision < 0 || typeof p.appliesToSharedMachines !== "boolean" || !(p.hostMachineId === null || id$2(p.hostMachineId)) || !["off", "ready", "host-offline", "host-unsupported", "not-authorized"].includes(String(p.status))) return null;
11609
11766
  if (p.enabled && !id$2(p.hostMachineId)) return null;
11610
11767
  return p;
11611
11768
  }
11612
11769
  function parseRoutingSettings(value2) {
11613
- const v = record$e(value2), s = record$e(v?.settings), r = record$e(v?.rollout), h = v?.host === null ? null : record$e(v?.host);
11770
+ const v = record$f(value2), s = record$f(v?.settings), r = record$f(v?.rollout), h = v?.host === null ? null : record$f(v?.host);
11614
11771
  if (!s || s.schemaVersion !== 1 || s.policyVersion !== ROUTING_POLICY || typeof s.enabled !== "boolean" || !(s.hostMachineId === null || id$2(s.hostMachineId)) || s.enabled && !id$2(s.hostMachineId) || !Number.isSafeInteger(s.revision) || s.revision < 0 || !(s.updatedById === null || id$2(s.updatedById)) || !(s.updatedAt === null || typeof s.updatedAt === "number" && Number.isFinite(s.updatedAt)) || !r || !["sharedMachineCount", "capableSharedMachineCount", "unsupportedSharedMachineCount"].every((k) => Number.isSafeInteger(r[k]) && r[k] >= 0) || v?.host !== null && (!h || !id$2(h.machineId) || !(h.displayName === null || typeof h.displayName === "string") || typeof h.online !== "boolean" || typeof h.shared !== "boolean" || !["ready", "missing", "unsupported", "not-ready"].includes(String(h.capability)) || !(h.hostProcessKeyId === null || id$2(h.hostProcessKeyId)))) throw new Error("Invalid organization routing settings");
11615
11772
  return v;
11616
11773
  }
11617
11774
  function canNegotiateOrgRouting(input) {
11618
- const capability = record$e(input.machine?.difficultyRouting);
11775
+ const capability = record$f(input.machine?.difficultyRouting);
11619
11776
  return !input.manual && id$2(input.requestId) && !!input.companyId && ROUTING_AGENTS.includes(input.agent ?? "") && input.machine?.registry?.type === "shared" && input.machine.registry.companyId === input.companyId && capability?.version === 1 && capability.protocol === ROUTING_POLICY;
11620
11777
  }
11621
11778
  function prepareRoutingIntent(input) {
@@ -15078,7 +15235,7 @@ const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
15078
15235
  inst.keyType = def.keyType;
15079
15236
  inst.valueType = def.valueType;
15080
15237
  });
15081
- function record$d(keyType, valueType, params) {
15238
+ function record$e(keyType, valueType, params) {
15082
15239
  return new ZodRecord({
15083
15240
  type: "record",
15084
15241
  keyType,
@@ -15312,7 +15469,7 @@ function superRefine(fn) {
15312
15469
  }
15313
15470
  function json(params) {
15314
15471
  const jsonSchema = lazy(() => {
15315
- return union([string$1(params), number(), boolean(), _null(), array$4(jsonSchema), record$d(string$1(), jsonSchema)]);
15472
+ return union([string$1(params), number(), boolean(), _null(), array$4(jsonSchema), record$e(string$1(), jsonSchema)]);
15316
15473
  });
15317
15474
  return jsonSchema;
15318
15475
  }
@@ -17610,14 +17767,14 @@ const scriptAutomationPayloadSchema = strictObject({
17610
17767
  entrypoint: entrypointSchema,
17611
17768
  args: array$4(string$1().max(4e3)).max(64),
17612
17769
  timeoutSeconds: number().int().min(1).max(1800),
17613
- secretRefs: record$d(secretName, string$1().min(1).max(200)),
17770
+ secretRefs: record$e(secretName, string$1().min(1).max(200)),
17614
17771
  allowedOrigins: array$4(originSchema).max(100)
17615
17772
  }),
17616
17773
  schedule: scriptScheduleSchema.nullable(),
17617
17774
  externalEnabled: boolean(),
17618
- inputSchema: record$d(string$1(), json())
17775
+ inputSchema: record$e(string$1(), json())
17619
17776
  });
17620
- strictObject({ input: record$d(string$1(), json()) });
17777
+ strictObject({ input: record$e(string$1(), json()) });
17621
17778
  const scriptAdmissionSchema = scriptAutomationPayloadSchema.pick({
17622
17779
  schedule: true,
17623
17780
  externalEnabled: true,
@@ -17644,7 +17801,7 @@ discriminatedUnion("operation", [
17644
17801
  strictObject({ operation: literal("upsert"), registration: scriptRegistrationRequestSchema }),
17645
17802
  strictObject({ operation: literal("runs"), automationId: managementId, runId: managementId.optional() }),
17646
17803
  strictObject({ operation: literal("artifact"), artifactId: managementId }),
17647
- strictObject({ operation: literal("run"), automationId: managementId, input: record$d(string$1(), json()), idempotencyKey: managementId }),
17804
+ strictObject({ operation: literal("run"), automationId: managementId, input: record$e(string$1(), json()), idempotencyKey: managementId }),
17648
17805
  strictObject({ operation: literal("cancel"), automationId: managementId, runId: managementId })
17649
17806
  ]);
17650
17807
  const scriptAutomationRowSchema = object$3({
@@ -17935,14 +18092,14 @@ function parseCloudLoginResult(raw) {
17935
18092
  }
17936
18093
  const HANDOFF_ID_PATTERN = /^mph-[A-Za-z0-9_-]{22}$/;
17937
18094
  const CAPABILITY_PATTERN = /^[A-Za-z0-9_-]{43}$/;
17938
- function record$c(value2) {
18095
+ function record$d(value2) {
17939
18096
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
17940
18097
  }
17941
18098
  function validExpiry$1(value2) {
17942
18099
  return typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0;
17943
18100
  }
17944
18101
  function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
17945
- const value2 = record$c(raw);
18102
+ const value2 = record$d(raw);
17946
18103
  if (!value2 || typeof value2.handoffId !== "string" || !HANDOFF_ID_PATTERN.test(value2.handoffId) || typeof value2.exchangeToken !== "string" || !CAPABILITY_PATTERN.test(value2.exchangeToken) || typeof value2.authorizationUrl !== "string" || !validExpiry$1(value2.expiresAt)) {
17947
18104
  throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
17948
18105
  }
@@ -17966,7 +18123,7 @@ function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
17966
18123
  };
17967
18124
  }
17968
18125
  function parsePendingMfaPasskeyExchange(raw) {
17969
- const value2 = record$c(raw);
18126
+ const value2 = record$d(raw);
17970
18127
  if (!value2 || value2.status !== "pending" || !validExpiry$1(value2.expiresAt)) {
17971
18128
  throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
17972
18129
  }
@@ -18205,6 +18362,9 @@ async function preDecryptSessionHeaders(sessions, accountPrivateKey, sinceGenera
18205
18362
  }
18206
18363
  }));
18207
18364
  }
18365
+ function readsAsStrictMachineControl(machine) {
18366
+ return !machine.serverDataEncryptionKey && typeof machine.serverRpcKeyEnvelope === "string" && machine.serverRpcKeyEnvelope !== "";
18367
+ }
18208
18368
  const MACHINE_KEY_ATTESTATION_VERSION = 1;
18209
18369
  const DIGEST_DOMAIN = new TextEncoder().encode("happy machine key fingerprint v1");
18210
18370
  const HEADER_BYTES = 1 + nacl.box.publicKeyLength + nacl.box.nonceLength;
@@ -18518,7 +18678,7 @@ function personalMachinesPath(companyId) {
18518
18678
  function myMachinesPath(companyId) {
18519
18679
  return `/api/admin/companies/${encodeURIComponent(companyId)}/my-machines`;
18520
18680
  }
18521
- function record$b(value2) {
18681
+ function record$c(value2) {
18522
18682
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
18523
18683
  }
18524
18684
  function array$3(value2) {
@@ -18537,7 +18697,7 @@ function provider(value2) {
18537
18697
  return PROVIDERS.includes(str$1(value2)) ? str$1(value2) : null;
18538
18698
  }
18539
18699
  function parseBundle(value2) {
18540
- const row2 = record$b(value2);
18700
+ const row2 = record$c(value2);
18541
18701
  const id2 = str$1(row2.id);
18542
18702
  const bundleProvider = provider(row2.provider);
18543
18703
  if (!id2 || !bundleProvider) return null;
@@ -18556,7 +18716,7 @@ function parseBundle(value2) {
18556
18716
  };
18557
18717
  }
18558
18718
  function parseDeployment(value2) {
18559
- const row2 = record$b(value2);
18719
+ const row2 = record$c(value2);
18560
18720
  const machineId = str$1(row2.machineId);
18561
18721
  const bundleId = str$1(row2.bundleId);
18562
18722
  if (!machineId || !bundleId) return null;
@@ -18576,10 +18736,10 @@ function parseDeployment(value2) {
18576
18736
  };
18577
18737
  }
18578
18738
  function parseStatus(value2) {
18579
- const row2 = record$b(value2);
18739
+ const row2 = record$c(value2);
18580
18740
  const statusProvider = provider(row2.provider);
18581
18741
  if (!statusProvider) return void 0;
18582
- const rotationRow = record$b(row2.rotation);
18742
+ const rotationRow = record$c(row2.rotation);
18583
18743
  const state = str$1(rotationRow.state);
18584
18744
  const warningKinds = Array.isArray(rotationRow.warningKinds) ? [...new Set(rotationRow.warningKinds.filter(
18585
18745
  (kind) => kind === "ACCOUNT_NEEDS_REAUTH" || kind === "NO_COMPARISON"
@@ -18604,7 +18764,7 @@ function parseStatus(value2) {
18604
18764
  };
18605
18765
  }
18606
18766
  function parseMachineResult(value2) {
18607
- const row2 = record$b(value2);
18767
+ const row2 = record$c(value2);
18608
18768
  const machineId = str$1(row2.machineId);
18609
18769
  if (!machineId) return null;
18610
18770
  if (row2.ok !== true) {
@@ -18619,28 +18779,28 @@ function parseMachineResult(value2) {
18619
18779
  };
18620
18780
  }
18621
18781
  function parseAiCredentialVaultSnapshot(value2) {
18622
- const body = record$b(value2);
18782
+ const body = record$c(value2);
18623
18783
  return {
18624
18784
  bundles: array$3(body.bundles).map(parseBundle).filter((bundle) => bundle !== null),
18625
18785
  deployments: array$3(body.deployments).map(parseDeployment).filter((deployment) => deployment !== null)
18626
18786
  };
18627
18787
  }
18628
18788
  function parseAiCredentialCaptureResponse(value2) {
18629
- const body = record$b(value2);
18789
+ const body = record$c(value2);
18630
18790
  const bundle = parseBundle(body.bundle);
18631
18791
  if (!bundle) throw new Error("AI credential capture response did not contain a bundle");
18632
18792
  return { bundle, warnings: array$3(body.warnings).filter((warning) => typeof warning === "string") };
18633
18793
  }
18634
18794
  function parseAiCredentialBatchResult(value2) {
18635
- const body = record$b(value2);
18795
+ const body = record$c(value2);
18636
18796
  return {
18637
18797
  results: array$3(body.results).map(parseMachineResult).filter((result2) => result2 !== null),
18638
18798
  warnings: array$3(body.warnings).filter((warning) => typeof warning === "string")
18639
18799
  };
18640
18800
  }
18641
18801
  function parseSelfServiceMachines(value2) {
18642
- return array$3(record$b(value2).machines).map((entry) => {
18643
- const row2 = record$b(entry);
18802
+ return array$3(record$c(value2).machines).map((entry) => {
18803
+ const row2 = record$c(entry);
18644
18804
  return {
18645
18805
  machineId: str$1(row2.machineId),
18646
18806
  type: row2.type === "shared" ? "shared" : "personal",
@@ -18653,8 +18813,8 @@ function parseSelfServiceMachines(value2) {
18653
18813
  }).filter((machine) => machine.machineId !== "");
18654
18814
  }
18655
18815
  function parsePersonalMachines(value2) {
18656
- return array$3(record$b(value2).machines).map((entry) => {
18657
- const row2 = record$b(entry);
18816
+ return array$3(record$c(value2).machines).map((entry) => {
18817
+ const row2 = record$c(entry);
18658
18818
  const type = row2.type === "shared" ? "shared" : "personal";
18659
18819
  return {
18660
18820
  machineId: str$1(row2.machineId),
@@ -18689,7 +18849,7 @@ const orgSearchPaths = {
18689
18849
  cursors: (companyId, projectId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/cursors${projectId === void 0 ? "" : `?projectId=${encodeURIComponent(projectId)}`}`,
18690
18850
  progress: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/progress`
18691
18851
  };
18692
- function record$a(value2) {
18852
+ function record$b(value2) {
18693
18853
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
18694
18854
  }
18695
18855
  function str(value2, fallback = "") {
@@ -18700,9 +18860,9 @@ function num(value2, fallback = 0) {
18700
18860
  }
18701
18861
  const PHASES = ["idle", "waiting", "indexing", "done"];
18702
18862
  function parseOrgSearchCursors(raw) {
18703
- const list2 = Array.isArray(record$a(raw).cursors) ? record$a(raw).cursors : [];
18863
+ const list2 = Array.isArray(record$b(raw).cursors) ? record$b(raw).cursors : [];
18704
18864
  return list2.flatMap((item) => {
18705
- const c = record$a(item);
18865
+ const c = record$b(item);
18706
18866
  if (typeof c.sessionId !== "string" || typeof c.lastSeq !== "number") return [];
18707
18867
  return [{
18708
18868
  sessionId: c.sessionId,
@@ -18714,7 +18874,7 @@ function parseOrgSearchCursors(raw) {
18714
18874
  });
18715
18875
  }
18716
18876
  function parseProjectProgress(raw) {
18717
- const p = record$a(raw);
18877
+ const p = record$b(raw);
18718
18878
  return {
18719
18879
  projectId: str(p.projectId),
18720
18880
  sessionsTotal: num(p.sessionsTotal),
@@ -18727,7 +18887,7 @@ function parseProjectProgress(raw) {
18727
18887
  };
18728
18888
  }
18729
18889
  function parseOrgSearchProgress(raw) {
18730
- const r = record$a(raw);
18890
+ const r = record$b(raw);
18731
18891
  return {
18732
18892
  phase: PHASES.includes(r.phase) ? r.phase : "idle",
18733
18893
  sessionsTotal: num(r.sessionsTotal),
@@ -19204,7 +19364,7 @@ const PUBLICATION_KEYS = /* @__PURE__ */ new Set([
19204
19364
  function fail$9(detail) {
19205
19365
  throw new Error(`artifact publication response is invalid: ${detail}`);
19206
19366
  }
19207
- function record$9(value2, label) {
19367
+ function record$a(value2, label) {
19208
19368
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) fail$9(`${label} must be an object`);
19209
19369
  return value2;
19210
19370
  }
@@ -19250,7 +19410,7 @@ function sourceIdentity(source2) {
19250
19410
  ]) : JSON.stringify([source2.chatId, source2.path]);
19251
19411
  }
19252
19412
  function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
19253
- const publication = record$9(value2, "publication");
19413
+ const publication = record$a(value2, "publication");
19254
19414
  if (Object.keys(publication).some((key) => !PUBLICATION_KEYS.has(key))) {
19255
19415
  fail$9("publication contains an unexpected field");
19256
19416
  }
@@ -19283,7 +19443,7 @@ function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
19283
19443
  };
19284
19444
  }
19285
19445
  function parseStaticArtifactPublicationResult(value2, options) {
19286
- const response2 = record$9(value2, "response");
19446
+ const response2 = record$a(value2, "response");
19287
19447
  if (options.operation === "publish") {
19288
19448
  exactKeys$3(response2, ["publication", "publicUrl", "reused"]);
19289
19449
  if (typeof response2.reused !== "boolean") fail$9("reused is invalid");
@@ -19305,20 +19465,20 @@ const MAX_ID = 256;
19305
19465
  function isRecord$4(value2) {
19306
19466
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
19307
19467
  }
19308
- function text$7(value2, max = MAX_TEXT) {
19468
+ function text$8(value2, max = MAX_TEXT) {
19309
19469
  return typeof value2 === "string" && value2.length >= 1 && value2.length <= max;
19310
19470
  }
19311
19471
  function list(value2, minItems) {
19312
- return Array.isArray(value2) && value2.length >= minItems && value2.length <= MAX_ITEMS && value2.every((item) => text$7(item));
19472
+ return Array.isArray(value2) && value2.length >= minItems && value2.length <= MAX_ITEMS && value2.every((item) => text$8(item));
19313
19473
  }
19314
19474
  function parseLessonCandidate(value2) {
19315
19475
  if (!isRecord$4(value2) || value2.t !== "lesson-candidate") return null;
19316
19476
  if (Object.keys(value2).some((key) => !EVENT_KEYS.has(key))) return null;
19317
19477
  const { candidateId, revision, payloadHash, lesson } = value2;
19318
- if (!text$7(candidateId, MAX_ID) || !text$7(payloadHash, MAX_ID)) return null;
19478
+ if (!text$8(candidateId, MAX_ID) || !text$8(payloadHash, MAX_ID)) return null;
19319
19479
  if (typeof revision !== "number" || !Number.isInteger(revision) || revision < 0) return null;
19320
19480
  if (!isRecord$4(lesson) || Object.keys(lesson).some((key) => !LESSON_KEYS.has(key))) return null;
19321
- if (!text$7(lesson.name) || !text$7(lesson.trigger) || !text$7(lesson.scope) || !text$7(lesson.reconsiderWhen)) return null;
19481
+ if (!text$8(lesson.name) || !text$8(lesson.trigger) || !text$8(lesson.scope) || !text$8(lesson.reconsiderWhen)) return null;
19322
19482
  if (!list(lesson.steps, 1) || !list(lesson.validation, 1) || !list(lesson.failureModes, 0)) return null;
19323
19483
  if (lesson.validVersions !== void 0 && !list(lesson.validVersions, 0)) return null;
19324
19484
  return {
@@ -19544,16 +19704,20 @@ const CLAUDE_PROVIDER_PATTERN = /rate.?limit|too many requests|overloaded|model[
19544
19704
  const INACTIVITY_PREFIX = "stopped automatically";
19545
19705
  const PROCESS_EXITED_NOTICE = "process exited unexpectedly";
19546
19706
  const CLAUDE_OAUTH_FAILURE = /^Failed to authenticate\. API Error: 401 OAuth access token is invalid\.$/i;
19547
- const CLAUDE_NOT_SIGNED_IN = /^(?:Not logged in · Please run \/login|Claude Code is not signed in on this machine\.)/i;
19707
+ const CLAUDE_NOT_SIGNED_IN = /^Not logged in · Please run \/login/i;
19548
19708
  const CLAUDE_SUBSCRIPTION_REFUSAL = /^Your organization has disabled Claude subscription access for Claude Code(?:\s*[·.—-]\s*Use an Anthropic API key instead, or ask your admin to enable access)?\.?$/i;
19549
19709
  function unsupportedModelSurface(text2) {
19550
19710
  if (detectModelLimitNotice(text2) || text2.toLowerCase().includes("requires a newer version of codex")) return null;
19551
19711
  return /\b(?:model[_ -](?:not[_ -]found|unsupported|not[_ -]supported)|unsupported[_ -]model|unknown model)\b|\bmodel(?:\s+["'`][^"'`\r\n]{1,120}["'`])?\s+(?:is not supported|is unsupported|does not exist)\b/i.test(text2) ? { layer: "provider", code: "model_unsupported", retryable: true } : null;
19552
19712
  }
19713
+ function capacitySurface(text2) {
19714
+ if (detectModelLimitNotice(text2) || text2.toLowerCase().includes("requires a newer version of codex")) return null;
19715
+ return /\bselected model is at capacity\b/i.test(text2) ? { layer: "provider", code: "model_capacity", retryable: true } : null;
19716
+ }
19553
19717
  function messageErrorSurface(message) {
19554
19718
  if ((message.role === "agent" || message.role === "system") && !message.thinking && !message.toolCall && message.errorSurface?.layer === "provider" && ["codex_error", "claude_error"].includes(message.errorSurface.code)) {
19555
19719
  const text22 = message.content ?? "";
19556
- const refined = !AUTH_PATTERNS.some((pattern) => pattern.test(text22.toLowerCase())) ? unsupportedModelSurface(text22) : null;
19720
+ const refined = !AUTH_PATTERNS.some((pattern) => pattern.test(text22.toLowerCase())) ? unsupportedModelSurface(text22) ?? (message.errorSurface.code === "codex_error" ? capacitySurface(text22) : null) : null;
19557
19721
  if (refined) return refined;
19558
19722
  }
19559
19723
  if (message.errorSurface && isErrorSurfaceLayer(message.errorSurface.layer)) return message.errorSurface;
@@ -19587,7 +19751,7 @@ function classifyErrorSurface(rawText) {
19587
19751
  if (lower.startsWith(CODEX_ERROR_PREFIX) && lower.includes("requires a newer version of codex")) {
19588
19752
  return { layer: "provider", code: "codex_needs_update", retryable: false };
19589
19753
  }
19590
- const modelFailure = unsupportedModelSurface(text2);
19754
+ const modelFailure = unsupportedModelSurface(text2) ?? (lower.startsWith(CODEX_ERROR_PREFIX) ? capacitySurface(text2) : null);
19591
19755
  if (modelFailure) return modelFailure;
19592
19756
  if (lower.startsWith(CLAUDE_ERROR_PREFIX) && !CLAUDE_PROVIDER_PATTERN.test(lower)) return null;
19593
19757
  return { layer: "provider", code: lower.startsWith(CODEX_ERROR_PREFIX) ? "codex_error" : "claude_error", retryable: true };
@@ -28764,6 +28928,53 @@ async function prepareMcpCallerGrantEnvelope(input, deps) {
28764
28928
  return { ok: false, reason: "invalid-key" };
28765
28929
  }
28766
28930
  }
28931
+ function record$9(value2) {
28932
+ return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
28933
+ }
28934
+ function text$7(value2) {
28935
+ return typeof value2 === "string" && value2.trim() ? value2.trim() : null;
28936
+ }
28937
+ function timestamp(value2, fallback) {
28938
+ if (typeof value2 === "number" && Number.isFinite(value2)) return value2;
28939
+ if (typeof value2 === "string" && value2.trim()) {
28940
+ const parsed2 = Date.parse(value2);
28941
+ if (Number.isFinite(parsed2)) return parsed2;
28942
+ }
28943
+ return fallback;
28944
+ }
28945
+ function parseInvite(value2) {
28946
+ const row2 = record$9(value2);
28947
+ if (!row2) return null;
28948
+ const inviteId = text$7(row2.inviteId ?? row2.id);
28949
+ const companyId = text$7(row2.companyId);
28950
+ const companyDisplayName = text$7(row2.companyDisplayName ?? row2.companyName);
28951
+ const companySlug = text$7(row2.companySlug ?? row2.slug);
28952
+ const role = row2.role === "admin" || row2.role === "member" ? row2.role : null;
28953
+ const createdAt = timestamp(row2.createdAt, null);
28954
+ if (!inviteId || !companyId || !companyDisplayName || !companySlug || !role || createdAt === null) return null;
28955
+ return {
28956
+ inviteId,
28957
+ companyId,
28958
+ companyDisplayName,
28959
+ companySlug,
28960
+ role,
28961
+ expiresAt: timestamp(row2.expiresAt, null),
28962
+ createdAt
28963
+ };
28964
+ }
28965
+ function parsePendingCompanyInvites(value2) {
28966
+ const rows = Array.isArray(value2) ? value2 : record$9(value2)?.invites;
28967
+ if (!Array.isArray(rows)) throw new Error("Invalid invitation response");
28968
+ const seen = /* @__PURE__ */ new Set();
28969
+ const result2 = [];
28970
+ for (const row2 of rows) {
28971
+ const invite = parseInvite(row2);
28972
+ if (!invite || seen.has(invite.inviteId)) continue;
28973
+ seen.add(invite.inviteId);
28974
+ result2.push(invite);
28975
+ }
28976
+ return result2;
28977
+ }
28767
28978
  const CLEARABLE_STRING_KEYS = /* @__PURE__ */ new Set(["branch", "baseBranch"]);
28768
28979
  function buildWorktreeMutationBody(input, options = {}) {
28769
28980
  return JSON.stringify(Object.fromEntries(
@@ -29535,6 +29746,7 @@ function normalizeMachine(row2, candidateKeys, dataKeyFields = null) {
29535
29746
  ...authenticatedEnvelopes ? { authenticatedEnvelopes } : {},
29536
29747
  // specs/e2ee-machine-control-boundary R22 — the attestation decision for the key this app opened.
29537
29748
  ...keyTrust ? { keyTrust } : {},
29749
+ ...readsAsStrictMachineControl(row2) ? { machineControl: "strict" } : {},
29538
29750
  channelSupport,
29539
29751
  // Owner metadata only: the host key in it is what Desktop seals credentials to.
29540
29752
  channelHost: parseChannelHostCapability(decrypted.channelHost),
@@ -29941,6 +30153,30 @@ class ApiClient {
29941
30153
  const data = await this.request("/api/companies/me", { boundary: "api" });
29942
30154
  return Array.isArray(data) ? data : data.companies ?? [];
29943
30155
  }
30156
+ async getPendingCompanyInvites() {
30157
+ const invites = [], cursors = /* @__PURE__ */ new Set();
30158
+ let cursor = null;
30159
+ do {
30160
+ const data = await this.request(`/api/companies/pending-invites-for-me?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`, { boundary: "api" });
30161
+ invites.push(...parsePendingCompanyInvites(data));
30162
+ cursor = !Array.isArray(data) && data && typeof data === "object" && "nextCursor" in data && typeof data.nextCursor === "string" ? data.nextCursor : null;
30163
+ if (cursor && cursors.has(cursor)) throw new Error("Repeated invitation cursor");
30164
+ if (cursor) cursors.add(cursor);
30165
+ } while (cursor);
30166
+ return invites.filter((row2, index) => invites.findIndex((other) => other.inviteId === row2.inviteId) === index);
30167
+ }
30168
+ async acceptCompanyInvite(inviteId, mfaSessionToken) {
30169
+ const data = await this.request("/api/companies/join", {
30170
+ method: "POST",
30171
+ boundary: "api",
30172
+ body: JSON.stringify({ inviteId }),
30173
+ headers: mfaSessionToken ? { "X-Saycode-Mfa-Session": mfaSessionToken } : {}
30174
+ });
30175
+ return { reauthenticationRequired: data.reauthenticationRequired === true };
30176
+ }
30177
+ async declineCompanyInvite(inviteId) {
30178
+ await this.request(`/api/companies/pending-invites-for-me/${encodeURIComponent(inviteId)}`, { method: "DELETE", boundary: "api" });
30179
+ }
29944
30180
  async getAiGroupAssignments(companyId, signal, mfaSessionToken) {
29945
30181
  const value2 = await this.request(`/api/companies/${encodeURIComponent(companyId)}/ai-groups/me`, {
29946
30182
  boundary: "api",
@@ -34465,7 +34701,7 @@ class ChatInputController {
34465
34701
  this.readline.off("close", this.handleClose);
34466
34702
  }
34467
34703
  }
34468
- function readString$3(source2, key) {
34704
+ function readString$4(source2, key) {
34469
34705
  const value2 = source2[key];
34470
34706
  return typeof value2 === "string" && value2 ? value2 : void 0;
34471
34707
  }
@@ -34484,8 +34720,8 @@ function normalizeSessionRows(rows, candidateKeys) {
34484
34720
  if (!metadata) continue;
34485
34721
  sessions.push({
34486
34722
  id: row2.id,
34487
- machineId: readString$3(metadata, "machineId") ?? "",
34488
- directory: readString$3(metadata, "path") ?? "",
34723
+ machineId: readString$4(metadata, "machineId") ?? "",
34724
+ directory: readString$4(metadata, "path") ?? "",
34489
34725
  summary: readSummary(metadata),
34490
34726
  active: row2.active === true,
34491
34727
  updatedAt: row2.updatedAt ?? 0,
@@ -37990,7 +38226,7 @@ async function replyToPermission(connection, request2, answer) {
37990
38226
  ...answer === "approve-always" ? { mode: "bypassPermissions" } : {}
37991
38227
  });
37992
38228
  }
37993
- function readString$2(source2, key) {
38229
+ function readString$3(source2, key) {
37994
38230
  const value2 = source2?.[key];
37995
38231
  return typeof value2 === "string" && value2 ? value2 : void 0;
37996
38232
  }
@@ -37999,9 +38235,9 @@ function normalizeMachineRows(rows, candidateKeys) {
37999
38235
  const metadata = decryptJsonWithCandidates(row2.metadata, candidateKeys);
38000
38236
  return {
38001
38237
  id: row2.id,
38002
- host: readString$2(metadata, "host") ?? row2.id.slice(0, 8),
38003
- platform: readString$2(metadata, "platform") ?? "unknown",
38004
- ...readString$2(metadata, "homeDir") ? { homeDir: readString$2(metadata, "homeDir") } : {},
38238
+ host: readString$3(metadata, "host") ?? row2.id.slice(0, 8),
38239
+ platform: readString$3(metadata, "platform") ?? "unknown",
38240
+ ...readString$3(metadata, "homeDir") ? { homeDir: readString$3(metadata, "homeDir") } : {},
38005
38241
  online: typeof row2.online === "boolean" ? row2.online : row2.active === true,
38006
38242
  // Unknown last-seen renders as "unknown", never as "just now" — same
38007
38243
  // reasoning as src/api/client.ts#normalizeMachine.
@@ -40352,7 +40588,7 @@ async function startFrontDoor(options) {
40352
40588
  return;
40353
40589
  }
40354
40590
  const target = upstreams[route];
40355
- const upstream = request({
40591
+ const upstream = request$1({
40356
40592
  host: target.hostname,
40357
40593
  port: target.port,
40358
40594
  method: req.method,
@@ -40721,9 +40957,9 @@ function serverLogWriter(layout, name) {
40721
40957
  };
40722
40958
  }
40723
40959
  const happyCliPackageName = "@buzzni/happy-cli";
40724
- const happyCliVersion = "1.1.10-aplus.318";
40725
- const happyCliTag = "happy-cli-v1.1.10-aplus.318";
40726
- const vendorCommit = "236706106627e00c5b212fab7f9f98ce3ea05d97";
40960
+ const happyCliVersion = "1.1.10-aplus.319";
40961
+ const happyCliTag = "happy-cli-v1.1.10-aplus.319";
40962
+ const vendorCommit = "4cc930893e9530c5d88ab0280575979b433738a3";
40727
40963
  const saycodeCliVersion = "0.8.2";
40728
40964
  const happyServerVersion = "0.1.2";
40729
40965
  const releaseFeatures = { "mcpRuntimeRecovery": { "enabled": true, "releaseState": "released", "sourceBranch": "main", "sourceHead": "a79a10e288cf7775cb15955f716693c6a7df1640", "requiredCommit": "975e32cf" } };
@@ -45901,7 +46137,7 @@ function requireLib$2() {
45901
46137
  }
45902
46138
  if (this.state !== PENDING) {
45903
46139
  var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
45904
- unwrap(promise, resolver, this.outcome);
46140
+ unwrap2(promise, resolver, this.outcome);
45905
46141
  } else {
45906
46142
  this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
45907
46143
  }
@@ -45922,15 +46158,15 @@ function requireLib$2() {
45922
46158
  handlers.resolve(this.promise, value2);
45923
46159
  };
45924
46160
  QueueItem.prototype.otherCallFulfilled = function(value2) {
45925
- unwrap(this.promise, this.onFulfilled, value2);
46161
+ unwrap2(this.promise, this.onFulfilled, value2);
45926
46162
  };
45927
46163
  QueueItem.prototype.callRejected = function(value2) {
45928
46164
  handlers.reject(this.promise, value2);
45929
46165
  };
45930
46166
  QueueItem.prototype.otherCallRejected = function(value2) {
45931
- unwrap(this.promise, this.onRejected, value2);
46167
+ unwrap2(this.promise, this.onRejected, value2);
45932
46168
  };
45933
- function unwrap(promise, func, value2) {
46169
+ function unwrap2(promise, func, value2) {
45934
46170
  immediate(function() {
45935
46171
  var returnValue;
45936
46172
  try {
@@ -54534,6 +54770,52 @@ const conversationTabsEn = {
54534
54770
  "workspaceHost.closeConversationHint": "Closes this tab. Your conversation history is kept.",
54535
54771
  "workspaceHost.newConversation": "Open a new conversation"
54536
54772
  };
54773
+ const personalAiEn = {
54774
+ "aiConnection.sectionTitle": "Check connected AI",
54775
+ "aiConnection.ai": "AI",
54776
+ "aiConnection.pc": "PC",
54777
+ "aiConnection.selectPc": "Select a PC",
54778
+ "aiConnection.pcUnknown": "Unavailable",
54779
+ "aiConnection.selectAi": "Select AI",
54780
+ "aiConnection.chooseAiHint": "Select an AI to check its connection on this PC.",
54781
+ "personalAi.settingsTitle": "My AI account",
54782
+ "personalAi.settingsDescription": "Connect the ChatGPT or Claude account you already use.",
54783
+ "personalAi.title": "Connect a personal AI account",
54784
+ "personalAi.imageAlt": "A web account connected to a desktop chat",
54785
+ "personalAi.intro": "Connect your ChatGPT or Claude account\nto chat with AI in SayCode.",
54786
+ "personalAi.note": "Sign in with the AI account you already use.",
54787
+ "personalAi.later": "Maybe later",
54788
+ "personalAi.connect": "Connect my AI account",
54789
+ "personalAi.choose": "Choose an AI service",
54790
+ "personalAi.chooseDescription": "Sign in on the selected service’s website.",
54791
+ "personalAi.withProvider": "Connect with {provider}",
54792
+ "personalAi.back": "Back",
54793
+ "personalAi.providerTitle": "Connect {provider}",
54794
+ "personalAi.waiting": "Finish signing in in your browser.",
54795
+ "personalAi.approval": "After signing in and approving access,\nwe’ll automatically check the connection.",
54796
+ "personalAi.cancel": "Cancel",
54797
+ "personalAi.reopen": "Reopen browser",
54798
+ "personalAi.expired": "We couldn’t confirm the connection yet.",
54799
+ "personalAi.retryDescription": "Reconnect and finish signing in\nin your browser.",
54800
+ "personalAi.retry": "Reconnect",
54801
+ "personalAi.connected": "Your {provider} account is connected.",
54802
+ "personalAi.ready": "Ready to use on this computer.",
54803
+ "personalAi.done": "Done",
54804
+ "personalAi.localOnly": "Connect personal AI accounts on this computer. Select this computer as your work PC, then reconnect.",
54805
+ "personalAi.unavailable": "Check this computer’s connection and AI tool installation, then open this again.",
54806
+ "aiConnection.loginLinkFailed": "Could not open the sign-in link. Select the link under Show terminal instead.",
54807
+ "aiConnection.openLoginLink": "Open sign-in link",
54808
+ "aiConnection.loginPreparing": "Preparing sign-in…",
54809
+ "aiConnection.codeLabel": "Paste the authentication code shown after signing in on the web page.",
54810
+ "aiConnection.codeSend": "Send code",
54811
+ "aiConnection.loginFailed": "Sign-in failed. Check the authentication code and try again.",
54812
+ "aiConnection.loginRetry": "Try again",
54813
+ "aiConnection.loginTerminalFailed": "Could not open the sign-in terminal. Reopen it from the terminal below.",
54814
+ "aiConnection.loginTerminalClosed": "The sign-in terminal closed. Reopen it from the terminal below.",
54815
+ "aiConnection.loginLinkSlow": "The sign-in link has not appeared yet. Check the progress in the terminal below.",
54816
+ "aiConnection.codeSent": "Code sent. When sign-in finishes, select Sign-in complete.",
54817
+ "aiConnection.showTerminal": "Show terminal"
54818
+ };
54537
54819
  const questionComposerEn = {
54538
54820
  "askUser.skip": "Skip",
54539
54821
  "askUser.skipAnswer": "The user skipped answering this question.",
@@ -54755,6 +55037,12 @@ const projectSettingsEn = {
54755
55037
  "projectSettings.diagnostics": "Developer information"
54756
55038
  };
54757
55039
  const aiRecoveryEn = {
55040
+ "aiRecovery.capacityTitle": "Model capacity recovery",
55041
+ "aiRecovery.capacitySummary": "There are a lot of requests right now. Please try again shortly.",
55042
+ "aiRecovery.capacityCountdown": "Retrying in {seconds}s ({attempt}/3)",
55043
+ "aiRecovery.capacityCancel": "Cancel",
55044
+ "aiRecovery.capacityExhausted": "Automatic retries finished. Retry later or change the model in the composer.",
55045
+ "aiRecovery.capacityModelHint": "You can choose another model in the composer.",
54758
55046
  "aiRecovery.modelUnsupported": "This AI cannot use the selected model. Change the model in the message box, then retry.",
54759
55047
  "aiRecovery.accessSummary": "This account cannot use Claude.",
54760
55048
  "aiRecovery.connectAccount": "Reconnect AI",
@@ -54872,6 +55160,8 @@ const managedClaudeEn = {
54872
55160
  "managedClaude.loading": "Connecting AI…"
54873
55161
  };
54874
55162
  const workEn = {
55163
+ "work.mediaChanged": "The original file changed. Reopen it to continue.",
55164
+ "work.mediaUnsupported": "Media streaming is unavailable on this computer. Save the original and open it locally.",
54875
55165
  "work.homeTitle": "What can I help you with?",
54876
55166
  "work.new": "Start new chat",
54877
55167
  "work.archived": "Archived chats",
@@ -54927,7 +55217,8 @@ const workEn = {
54927
55217
  "work.sourceNotice.denied": "You do not have access to this file. Check your permissions.",
54928
55218
  "work.sourceNotice.unavailable": "Could not check the file. Try again.",
54929
55219
  "work.saveCheck": "Check saved copy",
54930
- "work.checkingFile": "Checking…"
55220
+ "work.checkingFile": "Checking…",
55221
+ "work.mediaViewerNeeded": "Install a media preview extension and allow it to read the selected file."
54931
55222
  };
54932
55223
  const FILE_ACCESS_TRANSLATIONS_EN = {
54933
55224
  "fileAccess.retry": "Try again",
@@ -55003,6 +55294,8 @@ const organizationInstallRecoveryEn = {
55003
55294
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "The file could not run or report its version.",
55004
55295
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "Tool preparation failed. The exact cause is not yet known.",
55005
55296
  "organizationStart.toolDiagnostic.reason.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "The installed cswap does not support the required authentication capability.",
55297
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_AUTH_REQUIRED": "The company proxy asked for sign-in authentication, so the connection failed.",
55298
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_BLOCKED": "The company proxy did not allow the connection to the download or package server.",
55006
55299
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "Retry, or install the tool using the guide and check again.",
55007
55300
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "Prepare the listed prerequisite first, then retry.",
55008
55301
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "Check the internet, company network and proxy, then retry.",
@@ -55015,12 +55308,55 @@ const organizationInstallRecoveryEn = {
55015
55308
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "Check the network and retry. Share this diagnostic if it repeats.",
55016
55309
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "Check installation completeness and whether company policy permits execution.",
55017
55310
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "Retry. If it repeats, share this diagnostic.",
55018
- "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "Retry to install the authentication tool used by Saycode."
55311
+ "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "Retry to install the authentication tool used by Saycode.",
55312
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_AUTH_REQUIRED": "Share this diagnostic with IT and ask them to exempt the download server from proxy authentication.",
55313
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_BLOCKED": "Share this diagnostic with IT and ask them to allow access to the download server."
55314
+ };
55315
+ const organizationAccountKeyEn = {
55316
+ "organizationStart.accountKey.title": "Turn on conversation encryption",
55317
+ "organizationStart.accountKey.body": "To use organization AI on this computer, turn on conversation encryption first. Your current password creates this account's encryption key.",
55318
+ "organizationStart.accountKey.row": "Conversation encryption",
55319
+ "organizationStart.accountKey.passwordLabel": "Current password",
55320
+ "organizationStart.accountKey.checking": "Checking password…",
55321
+ "organizationStart.accountKey.recoveryTitle": "Keep your Recovery Key",
55322
+ "organizationStart.accountKey.recoveryNotice": "It opens your conversations if you forget your password. If you lose both your password and your Recovery Key, encrypted conversations cannot be opened. You cannot see this Recovery Key again after closing this screen.",
55323
+ "organizationStart.accountKey.copy": "Copy",
55324
+ "organizationStart.accountKey.copied": "Recovery Key copied.",
55325
+ "organizationStart.accountKey.save": "Save to file",
55326
+ "organizationStart.accountKey.saved": "Recovery Key saved.",
55327
+ "organizationStart.accountKey.confirmLabel": "Enter the Recovery Key again",
55328
+ "organizationStart.accountKey.confirmMismatch": "This does not match the Recovery Key above.",
55329
+ "organizationStart.accountKey.enable": "Turn on conversation encryption",
55330
+ "organizationStart.accountKey.saving": "Turning on…",
55331
+ "organizationStart.accountKey.renewBody": "The check timed out, so confirm your password once more. The Recovery Key you kept stays the same.",
55332
+ "organizationStart.accountKey.renew": "Check again",
55333
+ "organizationStart.accountKey.error.wrong-password": "The current password is incorrect.",
55334
+ "organizationStart.accountKey.error.rate-limited": "Too many attempts. Try again in a moment.",
55335
+ "organizationStart.accountKey.error.already-enabled": "Conversation encryption is already on for this account. Sign out and sign in again.",
55336
+ "organizationStart.accountKey.error.signed-out": "Your sign-in expired. Sign out and sign in again.",
55337
+ "organizationStart.accountKey.error.expired": "The check timed out. Enter your password again.",
55338
+ "organizationStart.accountKey.error.failed": "Couldn't turn on conversation encryption. Try again in a moment.",
55339
+ "organizationStart.accountKey.error.recovery-mismatch": "This does not match the Recovery Key above.",
55340
+ "organizationStart.accountKey.lockedTitle": "Sign in again",
55341
+ "organizationStart.accountKey.lockedBody": "Conversation encryption is on for this account, but it is not open on this computer yet. Sign out and sign in again to continue.",
55342
+ "organizationStart.accountKey.signInAgain": "Sign in again",
55343
+ "organizationStart.diagnostic.reason.accountKey": "Conversation encryption is not on for this account yet.",
55344
+ "organizationStart.diagnostic.action.accountKey": "Turn on conversation encryption in start-up preparation.",
55345
+ "organizationStart.diagnostic.reason.accountKeyLocked": "This account's encryption key is not open on this computer yet.",
55346
+ "organizationStart.diagnostic.action.accountKeyLocked": "Sign out and sign in again."
55347
+ };
55348
+ const organizationPcRegistrationEn = {
55349
+ "organizationStart.diagnostic.reason.pcNotAssigned": "This PC is not assigned to this account as a dedicated PC in the selected organization.",
55350
+ "organizationStart.diagnostic.action.pcNotAssigned": "Check that you selected the right organization. If so, ask an organization administrator to assign this PC to this account.",
55351
+ "organizationStart.diagnostic.reason.pcReregister": "This PC's earlier registration can't be used anymore.",
55352
+ "organizationStart.diagnostic.action.pcReregister": "Ask an organization administrator to register this PC again."
55019
55353
  };
55020
55354
  const organizationStartEn = {
55021
55355
  "organizationStart.toolsDetails": "Tool preparation details",
55022
55356
  "organizationStart.toolsProgress": "Tool preparation status",
55023
55357
  ...organizationInstallRecoveryEn,
55358
+ ...organizationAccountKeyEn,
55359
+ ...organizationPcRegistrationEn,
55024
55360
  "organizationStart.connectingTitle": "Connecting to your organization",
55025
55361
  "organizationStart.connectingBody": "Getting ready to work in {name}.",
55026
55362
  "organizationStart.welcome": "Start working with your organization",
@@ -55048,6 +55384,9 @@ const organizationStartEn = {
55048
55384
  "organizationStart.aiGroupConflict": "Your organization’s AI account settings need attention. Contact your organization administrator.",
55049
55385
  "organizationStart.aiChecking": "Connecting AI…",
55050
55386
  "organizationStart.connectLater": "Connect later",
55387
+ "organizationStart.skip": "Skip",
55388
+ "organizationStart.noAi.body": "{name} has no organization AI accounts yet. Skip this step and start with your own AI account.",
55389
+ "organizationStart.noAi.status": "No organization AI accounts",
55051
55390
  "organizationStart.existingConnection": "Select Next to connect this PC to {name}.",
55052
55391
  "organizationStart.failed": "Onboarding setup",
55053
55392
  "organizationStart.recovery.title": "The app needs repair",
@@ -55154,7 +55493,32 @@ const organizationStartEn = {
55154
55493
  "organizationStart.diagnostic.reason.registrationChanged": "This PC's organization registration was changed or deleted, so it cannot connect with this account.",
55155
55494
  "organizationStart.diagnostic.action.registrationChanged": "Ask your organization administrator to check this PC's registration under Organization admin → PCs, then try again.",
55156
55495
  "organizationStart.diagnostic.reason.publicVerify": "Shared credentials were applied, but usable AI authentication was not confirmed.",
55157
- "organizationStart.diagnostic.action.publicVerify": "Ask your administrator to restore the latest shared credentials, then reconnect."
55496
+ "organizationStart.diagnostic.action.publicVerify": "Ask your administrator to restore the latest shared credentials, then reconnect.",
55497
+ "companyInvite.title": "Organization invitations",
55498
+ "companyInvite.checkFailed": "Check organization invitations",
55499
+ "companyInvite.body": "You have new organization invitations. Choose which organizations to join.",
55500
+ "companyInvite.onboardingBody": "Review your organization invitations before choosing a work organization.",
55501
+ "companyInvite.member": "Member",
55502
+ "companyInvite.admin": "Admin",
55503
+ "companyInvite.accept": "Accept",
55504
+ "companyInvite.decline": "Decline",
55505
+ "companyInvite.later": "Later",
55506
+ "companyInvite.dismiss": "Close organization invitations",
55507
+ "companyInvite.accepted": "You joined the organization.",
55508
+ "companyInvite.declined": "Organization invitation declined.",
55509
+ "companyInvite.failed": "We couldn't process the invitation. Try again later.",
55510
+ "companyInvite.reauth": "Sign in again to apply the administrator role.",
55511
+ "companyInvite.retry": "Check again",
55512
+ "companyInvite.securityRequired": "Admin invitations need security verification. Check your security settings and verification on the web, then retry.",
55513
+ "companyInvite.expiry": "Expires {date}",
55514
+ "companyInvite.declineConfirm": "Confirm decline",
55515
+ "companyInvite.declineBody": "Decline the invitation from {name}? Joining later requires a new invitation.",
55516
+ "companyInvite.cancel": "Cancel",
55517
+ "companyInvite.loading": "Checking invitations…",
55518
+ "companyInvite.acceptLabel": "Accept invitation from {name}",
55519
+ "companyInvite.declineLabel": "Decline invitation from {name}",
55520
+ "companyInvite.declineConfirmLabel": "Confirm decline of invitation from {name}",
55521
+ "companyInvite.refreshFailed": "Could not refresh invitations. Check again."
55158
55522
  };
55159
55523
  const usageEntryEn = {
55160
55524
  ...selfHostConnectEn,
@@ -55294,7 +55658,7 @@ const usageEntryEn = {
55294
55658
  "localTools.prerequisitesBody": "Check uv, Git and Node.js (including npm). Check again after installing.",
55295
55659
  "localTools.prerequisitesTitle": "Development prerequisites",
55296
55660
  "onboarding.preparationComplete": "Required setup complete",
55297
- "onboarding.additionalStepsBody": "Saycode instructions, AI accounts, notifications, and computer & browser control are optional. You can start now and configure them later.",
55661
+ "onboarding.additionalStepsBody": "Saycode instructions, AI accounts, and notifications are optional. You can start now and configure them later.",
55298
55662
  "onboarding.additionalStep": "Additional step",
55299
55663
  "onboarding.requiredSettings": "Required setup",
55300
55664
  "onboarding.additionalSettings": "Additional setup — optional",
@@ -55377,6 +55741,11 @@ const projectRelocationEN = {
55377
55741
  "sessionCreation.workspaceNotSet": "This machine’s workspace path is not set. Please set the machine workspace first."
55378
55742
  };
55379
55743
  const chatSchedulesEn = {
55744
+ "chatSchedules.chatTitle": "Schedules for this chat",
55745
+ "chatSchedules.sharedHint": "These same tasks are also available in the schedules menu.",
55746
+ "chatSchedules.allSchedules": "View all schedules",
55747
+ "chatSchedules.advanced": "Advanced automation settings",
55748
+ "chatSchedules.advancedHint": "Configure the AI, execution conditions and script automations. AI tasks are shared with the list above; script automations are a separate execution type.",
55380
55749
  "chatSchedules.olderReplies": "Earlier replies",
55381
55750
  "chatSchedules.newerReplies": "Newer replies",
55382
55751
  "chatSchedules.limitedReplies": "Showing part of this run. Open the conversation to read the full replies.",
@@ -55822,7 +56191,12 @@ const machineUsageEn = {
55822
56191
  "machineUsage.autoSwitched": "Automatically switched {provider}: {from} → {to}",
55823
56192
  "machineUsage.pinned": "pinned",
55824
56193
  "machineUsage.codexAccounts": "Codex accounts",
55825
- "machineUsage.usageStale": "refreshing usage"
56194
+ "machineUsage.usageStale": "refreshing usage",
56195
+ "machineUsage.autoRotation": "Auto account rotation",
56196
+ "machineUsage.autoRotationOn": "Switches to another account when limits get close.",
56197
+ "machineUsage.autoRotationOff": "Keeps using the selected account.",
56198
+ "machineUsage.autoRotationPaused": "Auto rotation turned off because you picked an account.",
56199
+ "machineUsage.autoRotationFailed": "Couldn't change auto rotation. Please try again."
55826
56200
  };
55827
56201
  const machineResourceMetricsEn = {
55828
56202
  "machineResourceMetrics.title": "Session machine CPU and memory",
@@ -56237,7 +56611,7 @@ const outboundWebhookEn = {
56237
56611
  };
56238
56612
  const messengerChannelsEn = {
56239
56613
  "settings.messengerChannels.title": "Messenger channels",
56240
- "settings.messengerChannels.privacyNotice": "Only the request's status and its final answer are sent to the connected messenger — never raw tool output, history, or attachments. Messenger transport is outside Saycode's end-to-end encryption.",
56614
+ "settings.messengerChannels.privacyNotice": "The request's status and its final answer are sent to the connected messenger; on Slack, so are files the agent names in that answer from its project folder. Raw tool output and history are never sent. Messenger transport is outside Saycode's end-to-end encryption.",
56241
56615
  "settings.messengerChannels.alwaysOnTitle": "Keep channels connected after quitting",
56242
56616
  "settings.messengerChannels.alwaysOnHelp": "Receive messages and continue work after quitting Saycode. Your computer must be awake and connected to the internet.",
56243
56617
  "settings.messengerChannels.alwaysOnBlocked": "Not available: this device cannot store the bot credential securely, so nothing could receive once Desktop exits.",
@@ -56395,7 +56769,7 @@ const messengerQuickSetupEn = {
56395
56769
  "settings.messengerChannels.quickSlack.openInSlack": "Open in Slack",
56396
56770
  "settings.messengerChannels.quickSlack.waiting": "Once you send it in Slack, the connection request appears here. Allow it and you are done.",
56397
56771
  "settings.messengerChannels.quickSlack.pairedWith": "Connected to {name}'s Slack",
56398
- "settings.messengerChannels.quickSlack.connectedHelp": "Just talk to the bot in the DM you sent the code from. Mention the bot and send /help to see commands.",
56772
+ "settings.messengerChannels.quickSlack.connectedHelp": "Just talk to the bot in the DM you sent the code from. In a channel you invited the bot to, mention it and it answers in a thread under your message (visible to channel members). Mention the bot and send /help to see commands.",
56399
56773
  "settings.messengerChannels.quickDiscord.title": "Connect Discord",
56400
56774
  "settings.messengerChannels.quickDiscord.subtitle": "Hand work to Saycode from Discord. Two steps and you are done.",
56401
56775
  "settings.messengerChannels.quickDiscord.installTitle": "Discord is not turned on yet",
@@ -56700,6 +57074,9 @@ const extensionPanelEn = {
56700
57074
  const extensionInstallEn = {
56701
57075
  "extensions.installOfficial": "Install extension",
56702
57076
  "extensions.installNamedOfficial": "Install {name}",
57077
+ "extensions.officialUpdate": "Update to {version}",
57078
+ "extensions.updateSaved": "The new version was installed, but finishing it could not be confirmed. Check the extension again.",
57079
+ "extensions.projectToolHint": "Open it from the More (⋮) menu of the right work area in a project conversation: {tools}",
56703
57080
  "extensions.officialName.artifactPublisher": "Public link extension",
56704
57081
  "extensions.officialName.pluginManager": "Plugin manager extension",
56705
57082
  "extensions.officialName.projectTemplates": "Project templates extension",
@@ -56728,25 +57105,26 @@ const extensionInstallEn = {
56728
57105
  "artifactPublication.error.network": "Could not reach the server. Check your network connection.",
56729
57106
  "artifactPublication.error.unauthenticated": "Your session expired. Sign in again and retry.",
56730
57107
  "artifactPublication.error.notFound": "The file was not found. Make sure it has been saved.",
57108
+ "artifactPublication.error.notFoundInFolder": "{file} was not found in the working folder {folder}. If it was created in another worktree, move it into this folder and try again.",
56731
57109
  "artifactPublication.error.tooLarge": "The file is too large. A published HTML file must be 10MB or smaller.",
56732
57110
  "artifactPublication.error.rateLimited": "Too many public link requests. Please try again in a moment.",
56733
57111
  "artifactPublication.error.rejected": "The server rejected the request to publish this file.",
56734
57112
  "artifactPublication.error.serverError": "The server hit an error. Please try again in a moment.",
56735
57113
  "artifactPublication.error.extensionUnavailable": "The extension is disabled or missing the publish permission. Check Artifact Publisher in settings.",
56736
- "artifactPublication.error.invalidResponse": "The server response could not be verified, so the action stopped."
57114
+ "artifactPublication.error.invalidResponse": "The server response could not be verified, so the action stopped.",
57115
+ "extensions.permission.filePreviewsRead": "Read preview content of files opened in the app. Saycode Core keeps source paths and credentials private."
56737
57116
  };
56738
57117
  const artifactLibraryTranslations$2 = {
56739
57118
  "artifactLibrary.title": "Artifacts",
56740
57119
  "artifactLibrary.scope": "Scope",
56741
57120
  "artifactLibrary.conversation": "This conversation",
56742
57121
  "artifactLibrary.project": "This project",
56743
- "artifactLibrary.all": "All projects",
56744
57122
  "artifactLibrary.search": "Search files, projects, conversations and paths",
56745
- "artifactLibrary.hint": "Documents and previews from conversations. Opens the original file.",
56746
57123
  "artifactLibrary.loading": "Finding artifacts…",
56747
57124
  "artifactLibrary.error": "Some conversations could not be loaded. Please retry.",
56748
57125
  "artifactLibrary.empty": "No artifacts yet.",
56749
57126
  "artifactLibrary.noMatches": "No matching artifacts.",
57127
+ "artifactLibrary.readUnsupported": "This PC does not support opening original work files. This feature currently supports macOS work PCs.",
56750
57128
  "artifactLibrary.retry": "Retry"
56751
57129
  };
56752
57130
  const personalChatsLandingEn = {
@@ -57610,9 +57988,15 @@ const sidebarUpdateEn = {
57610
57988
  "sidebarUpdate.completedBody": "Saycode v{version} has been updated.",
57611
57989
  "sidebarUpdate.installing": "Applying update…",
57612
57990
  "sidebarUpdate.update": "Update",
57613
- "sidebarUpdate.restartNotice": "Your sessions are saved before restarting.",
57991
+ "sidebarUpdate.restartNotice": "Your sessions are saved before restarting. Saycode reopens on its own when the install finishes.",
57614
57992
  "sidebarUpdate.minimize": "Minimize",
57615
- "sidebarUpdate.releaseNotes": "Release notes"
57993
+ "sidebarUpdate.releaseNotes": "Release notes",
57994
+ "sidebarUpdate.relaunchWaitTitle": "Installing the update",
57995
+ "sidebarUpdate.relaunchWaitBody": "Saycode v{version} opens on its own when the install finishes. Closing this window does not stop the install.",
57996
+ "sidebarUpdate.relaunchTimeoutTitle": "The update did not finish installing",
57997
+ "sidebarUpdate.relaunchTimeoutBody": "You can open the current version (v{version}) and get the update again from the app.",
57998
+ "sidebarUpdate.relaunchOpenCurrent": "Open current version",
57999
+ "sidebarUpdate.relaunchQuit": "Quit"
57616
58000
  };
57617
58001
  const globalPauseEn = {
57618
58002
  "settings.globalPause.enable": "Global pause (block new automatic actions)",
@@ -58937,7 +59321,6 @@ const onboardingEn = {
58937
59321
  "onboarding.page.addProject": "Add your first project",
58938
59322
  "onboarding.page.configured": "Configured",
58939
59323
  "onboarding.page.optional": "Optional",
58940
- "onboarding.page.computerReveal": "Internal test: show computer and browser control",
58941
59324
  "onboarding.page.testNotification": "Send test notification",
58942
59325
  "onboarding.page.testNotificationSending": "Sending test notification…",
58943
59326
  "onboarding.page.testNotificationSuccess": "Test notification sent.",
@@ -59112,6 +59495,7 @@ const en = {
59112
59495
  "aiModelPolicy.machineChanged": "Chat creation stopped because the selected PC is unavailable. Check the PC before sending again.",
59113
59496
  "aiModelPolicy.policyChanged": "Chat creation stopped because the allowed model policy changed. Review your question and attachments before sending again.",
59114
59497
  "aiModelPolicy.noModels": "No allowed models are available on this computer.",
59498
+ ...personalAiEn,
59115
59499
  ...conversationTabsEn,
59116
59500
  ...questionComposerEn,
59117
59501
  ...aiRecoveryEn,
@@ -61369,6 +61753,9 @@ const en = {
61369
61753
  "projectGitView.branchRename": "Rename branch",
61370
61754
  "projectGitView.branchDelete": "Delete branch",
61371
61755
  "terminal.connectTimeout": "Terminal connection timed out.",
61756
+ "terminal.strictMachineUnsupported": "Remote terminals are not available yet on machines in strict machine control. Use a terminal on the machine itself.",
61757
+ "terminal.machineControlUnknown": "This machine's CLI does not report its security mode, so a remote terminal cannot be opened. Update the happy CLI on the machine.",
61758
+ "terminal.machineKeyUnavailable": "This machine's key is not available, so the remote terminal was not opened. Unlock your account key and try again.",
61372
61759
  "terminal.connectFailed": "Failed to connect to the terminal.",
61373
61760
  "terminal.cwdPrepFailed": "Failed to prepare the remote terminal working directory.",
61374
61761
  "terminal.restoredOutputDivider": "Previous session output",
@@ -61870,6 +62257,52 @@ const conversationTabsZh = {
61870
62257
  "workspaceHost.closeConversationHint": "仅关闭标签页,对话记录会保留。",
61871
62258
  "workspaceHost.newConversation": "打开新对话"
61872
62259
  };
62260
+ const personalAiZh = {
62261
+ "aiConnection.sectionTitle": "查看已连接的AI",
62262
+ "aiConnection.ai": "AI",
62263
+ "aiConnection.pc": "PC",
62264
+ "aiConnection.selectPc": "选择PC",
62265
+ "aiConnection.pcUnknown": "无法确认",
62266
+ "aiConnection.selectAi": "选择AI",
62267
+ "aiConnection.chooseAiHint": "选择AI后,即可查看此PC的连接状态。",
62268
+ "personalAi.settingsTitle": "我的AI账号",
62269
+ "personalAi.settingsDescription": "连接您正在使用的ChatGPT或Claude账号。",
62270
+ "personalAi.title": "连接个人AI账号",
62271
+ "personalAi.imageAlt": "网页账号连接到桌面对话界面的示意图",
62272
+ "personalAi.intro": "连接ChatGPT或Claude账号,\n即可在SayCode中与AI对话。",
62273
+ "personalAi.note": "请使用您正在使用的AI账号登录。",
62274
+ "personalAi.later": "以后再说",
62275
+ "personalAi.connect": "连接我的AI账号",
62276
+ "personalAi.choose": "请选择要连接的AI",
62277
+ "personalAi.chooseDescription": "在所选服务的网站上登录。",
62278
+ "personalAi.withProvider": "使用{provider}账号连接",
62279
+ "personalAi.back": "返回",
62280
+ "personalAi.providerTitle": "连接{provider}账号",
62281
+ "personalAi.waiting": "请在浏览器中完成登录。",
62282
+ "personalAi.approval": "完成登录并授权连接后,\n将自动确认连接状态。",
62283
+ "personalAi.cancel": "取消",
62284
+ "personalAi.reopen": "重新打开浏览器",
62285
+ "personalAi.expired": "暂时无法确认连接。",
62286
+ "personalAi.retryDescription": "请重新连接并在浏览器中\n完成登录。",
62287
+ "personalAi.retry": "重新连接",
62288
+ "personalAi.connected": "已连接{provider}账号。",
62289
+ "personalAi.ready": "已准备好在此电脑上使用。",
62290
+ "personalAi.done": "完成",
62291
+ "personalAi.localOnly": "个人AI账号只能在此电脑上连接。请选择此电脑作为工作电脑后再连接。",
62292
+ "personalAi.unavailable": "请检查此电脑的连接和AI工具安装情况,然后重新打开。",
62293
+ "aiConnection.loginLinkFailed": "无法打开登录链接。请在显示终端中直接点击链接。",
62294
+ "aiConnection.openLoginLink": "打开登录链接",
62295
+ "aiConnection.loginPreparing": "正在准备登录…",
62296
+ "aiConnection.codeLabel": "请粘贴在浏览器登录后显示的验证码。",
62297
+ "aiConnection.codeSend": "发送验证码",
62298
+ "aiConnection.loginFailed": "登录失败。请检查验证码后重试。",
62299
+ "aiConnection.loginRetry": "重试",
62300
+ "aiConnection.loginTerminalFailed": "无法打开登录终端。请在下方终端中重新打开。",
62301
+ "aiConnection.loginTerminalClosed": "登录终端已关闭。请在下方终端中重新打开。",
62302
+ "aiConnection.loginLinkSlow": "登录链接尚未出现。请在下方终端中查看进度。",
62303
+ "aiConnection.codeSent": "验证码已发送。登录完成后请点击登录完成。",
62304
+ "aiConnection.showTerminal": "显示终端"
62305
+ };
61873
62306
  const questionComposerZh = {
61874
62307
  "askUser.skip": "跳过",
61875
62308
  "askUser.skipAnswer": "用户跳过了这个问题的回答。",
@@ -62091,6 +62524,12 @@ const projectSettingsZh = {
62091
62524
  "projectSettings.diagnostics": "开发信息"
62092
62525
  };
62093
62526
  const aiRecoveryZh = {
62527
+ "aiRecovery.capacityTitle": "模型繁忙重试",
62528
+ "aiRecovery.capacitySummary": "当前请求较多。请稍后重试。",
62529
+ "aiRecovery.capacityCountdown": "{seconds}秒后重试 ({attempt}/3)",
62530
+ "aiRecovery.capacityCancel": "取消",
62531
+ "aiRecovery.capacityExhausted": "自动重试已结束。请稍后重试或在输入框中更换模型。",
62532
+ "aiRecovery.capacityModelHint": "可以在输入框中选择其他模型。",
62094
62533
  "aiRecovery.modelUnsupported": "当前 AI 无法使用所选模型。请在输入框中更换模型后重试。",
62095
62534
  "aiRecovery.accessSummary": "此账号无法使用Claude。",
62096
62535
  "aiRecovery.connectAccount": "重新连接AI",
@@ -62208,6 +62647,8 @@ const managedClaudeZh = {
62208
62647
  "managedClaude.loading": "正在连接AI…"
62209
62648
  };
62210
62649
  const workZh = {
62650
+ "work.mediaChanged": "原文件已更改。请重新打开。",
62651
+ "work.mediaUnsupported": "此计算机不支持媒体流式播放。请保存原文件并在本地打开。",
62211
62652
  "work.homeTitle": "需要我帮您完成什么工作?",
62212
62653
  "work.new": "开始新chat",
62213
62654
  "work.archived": "已归档chat",
@@ -62263,7 +62704,8 @@ const workZh = {
62263
62704
  "work.sourceNotice.denied": "您无权访问此文件。请检查权限。",
62264
62705
  "work.sourceNotice.unavailable": "无法检查文件。请重试。",
62265
62706
  "work.saveCheck": "检查保存的副本",
62266
- "work.checkingFile": "检查中…"
62707
+ "work.checkingFile": "检查中…",
62708
+ "work.mediaViewerNeeded": "请安装媒体预览扩展,并允许其读取所选文件。"
62267
62709
  };
62268
62710
  const FILE_ACCESS_TRANSLATIONS_ZH = {
62269
62711
  "fileAccess.retry": "重试",
@@ -62339,6 +62781,8 @@ const organizationInstallRecoveryZh = {
62339
62781
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "无法运行文件或检查版本。",
62340
62782
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "工具准备失败,具体原因尚未确认。",
62341
62783
  "organizationStart.toolDiagnostic.reason.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "已安装的 cswap 不支持所需认证功能。",
62784
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_AUTH_REQUIRED": "公司代理要求登录认证,因此无法连接。",
62785
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_BLOCKED": "公司代理不允许连接下载或软件包服务器。",
62342
62786
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "请重试,或按指南安装后再次检查。",
62343
62787
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "请先准备所列前置工具,然后重试。",
62344
62788
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "请检查互联网、公司网络和代理,然后重试。",
@@ -62351,12 +62795,55 @@ const organizationInstallRecoveryZh = {
62351
62795
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "请检查网络后重试;若重复发生,请提供诊断。",
62352
62796
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "请检查安装完整性及公司策略是否允许执行。",
62353
62797
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "请重试;若重复发生,请提供此诊断。",
62354
- "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "请重试以安装 Saycode 使用的认证工具。"
62798
+ "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "请重试以安装 Saycode 使用的认证工具。",
62799
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_AUTH_REQUIRED": "请将此诊断提供给 IT,请求将下载服务器设为代理认证例外。",
62800
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_BLOCKED": "请将此诊断提供给 IT,请求允许访问下载服务器。"
62801
+ };
62802
+ const organizationAccountKeyZh = {
62803
+ "organizationStart.accountKey.title": "开启对话加密",
62804
+ "organizationStart.accountKey.body": "要在这台电脑上使用组织 AI,请先开启对话加密。将使用当前密码创建此账号的加密密钥。",
62805
+ "organizationStart.accountKey.row": "对话加密",
62806
+ "organizationStart.accountKey.passwordLabel": "当前密码",
62807
+ "organizationStart.accountKey.checking": "正在验证密码…",
62808
+ "organizationStart.accountKey.recoveryTitle": "请保管好恢复密钥",
62809
+ "organizationStart.accountKey.recoveryNotice": "忘记密码时可用它打开对话。如果密码和恢复密钥都丢失,将无法打开已加密的对话。关闭此页面后将无法再次查看恢复密钥。",
62810
+ "organizationStart.accountKey.copy": "复制",
62811
+ "organizationStart.accountKey.copied": "已复制恢复密钥。",
62812
+ "organizationStart.accountKey.save": "保存为文件",
62813
+ "organizationStart.accountKey.saved": "已保存恢复密钥。",
62814
+ "organizationStart.accountKey.confirmLabel": "再次输入恢复密钥",
62815
+ "organizationStart.accountKey.confirmMismatch": "输入的恢复密钥与上方的不一致。",
62816
+ "organizationStart.accountKey.enable": "开启对话加密",
62817
+ "organizationStart.accountKey.saving": "正在开启…",
62818
+ "organizationStart.accountKey.renewBody": "验证已超时,请再确认一次密码。已保管的恢复密钥保持不变。",
62819
+ "organizationStart.accountKey.renew": "重新验证",
62820
+ "organizationStart.accountKey.error.wrong-password": "当前密码不正确。",
62821
+ "organizationStart.accountKey.error.rate-limited": "尝试次数过多,请稍后再试。",
62822
+ "organizationStart.accountKey.error.already-enabled": "此账号已开启对话加密。请退出登录后重新登录。",
62823
+ "organizationStart.accountKey.error.signed-out": "登录已过期。请退出登录后重新登录。",
62824
+ "organizationStart.accountKey.error.expired": "验证已超时,请重新输入密码。",
62825
+ "organizationStart.accountKey.error.failed": "未能开启对话加密,请稍后再试。",
62826
+ "organizationStart.accountKey.error.recovery-mismatch": "输入的恢复密钥与上方的不一致。",
62827
+ "organizationStart.accountKey.lockedTitle": "请重新登录",
62828
+ "organizationStart.accountKey.lockedBody": "此账号已开启对话加密,但在这台电脑上尚未打开。退出登录后重新登录即可继续。",
62829
+ "organizationStart.accountKey.signInAgain": "重新登录",
62830
+ "organizationStart.diagnostic.reason.accountKey": "此账号尚未开启对话加密。",
62831
+ "organizationStart.diagnostic.action.accountKey": "请在启动准备中开启对话加密。",
62832
+ "organizationStart.diagnostic.reason.accountKeyLocked": "这台电脑上尚未打开账号加密密钥。",
62833
+ "organizationStart.diagnostic.action.accountKeyLocked": "请退出登录后重新登录。"
62834
+ };
62835
+ const organizationPcRegistrationZh = {
62836
+ "organizationStart.diagnostic.reason.pcNotAssigned": "这台电脑在所选组织中未被分配为此账号的专用电脑。",
62837
+ "organizationStart.diagnostic.action.pcNotAssigned": "请确认所选组织是否正确;若正确,请联系组织管理员将这台电脑分配为此账号的专用电脑。",
62838
+ "organizationStart.diagnostic.reason.pcReregister": "无法继续使用这台电脑之前的注册信息。",
62839
+ "organizationStart.diagnostic.action.pcReregister": "请联系组织管理员重新注册这台电脑。"
62355
62840
  };
62356
62841
  const organizationStartZh = {
62357
62842
  "organizationStart.toolsDetails": "工具准备详情",
62358
62843
  "organizationStart.toolsProgress": "工具准备状态",
62359
62844
  ...organizationInstallRecoveryZh,
62845
+ ...organizationAccountKeyZh,
62846
+ ...organizationPcRegistrationZh,
62360
62847
  "organizationStart.connectingTitle": "正在连接组织",
62361
62848
  "organizationStart.connectingBody": "正在准备进入{name}开展工作。",
62362
62849
  "organizationStart.welcome": "立即在组织中开始",
@@ -62384,6 +62871,9 @@ const organizationStartZh = {
62384
62871
  "organizationStart.aiGroupConflict": "需要检查组织的AI账号设置。请联系组织管理员。",
62385
62872
  "organizationStart.aiChecking": "正在连接AI…",
62386
62873
  "organizationStart.connectLater": "稍后连接",
62874
+ "organizationStart.skip": "跳过",
62875
+ "organizationStart.noAi.body": "{name} 尚未注册组织 AI 账号。可以跳过此步骤,使用自己的 AI 账号开始。",
62876
+ "organizationStart.noAi.status": "没有组织 AI 账号",
62387
62877
  "organizationStart.existingConnection": "要将此电脑连接到{name},请点击“下一步”。",
62388
62878
  "organizationStart.failed": "入门准备",
62389
62879
  "organizationStart.recovery.title": "需要修复应用",
@@ -62490,7 +62980,32 @@ const organizationStartZh = {
62490
62980
  "organizationStart.diagnostic.reason.registrationChanged": "此电脑的组织登记已被更改或删除,无法用此账号连接。",
62491
62981
  "organizationStart.diagnostic.action.registrationChanged": "请联系组织管理员在 组织管理 → 电脑 中检查此电脑的登记,然后重试。",
62492
62982
  "organizationStart.diagnostic.reason.publicVerify": "共享认证已应用,但未能确认 AI 认证可用。",
62493
- "organizationStart.diagnostic.action.publicVerify": "请组织管理员恢复最新共享认证,然后重新连接。"
62983
+ "organizationStart.diagnostic.action.publicVerify": "请组织管理员恢复最新共享认证,然后重新连接。",
62984
+ "companyInvite.title": "组织邀请",
62985
+ "companyInvite.checkFailed": "需要检查组织邀请",
62986
+ "companyInvite.body": "您收到了新的组织邀请。请选择要加入的组织。",
62987
+ "companyInvite.onboardingBody": "选择工作组织前,请先查看收到的组织邀请。",
62988
+ "companyInvite.member": "成员",
62989
+ "companyInvite.admin": "管理员",
62990
+ "companyInvite.accept": "接受",
62991
+ "companyInvite.decline": "拒绝",
62992
+ "companyInvite.later": "稍后",
62993
+ "companyInvite.dismiss": "关闭组织邀请",
62994
+ "companyInvite.accepted": "您已加入组织。",
62995
+ "companyInvite.declined": "已拒绝组织邀请。",
62996
+ "companyInvite.failed": "无法处理此邀请,请稍后重试。",
62997
+ "companyInvite.reauth": "请重新登录以应用管理员权限。",
62998
+ "companyInvite.retry": "重新检查",
62999
+ "companyInvite.securityRequired": "管理员邀请需要安全验证。请在网页检查安全设置和验证后重试。",
63000
+ "companyInvite.expiry": "到期:{date}",
63001
+ "companyInvite.declineConfirm": "确认拒绝",
63002
+ "companyInvite.declineBody": "拒绝{name}的邀请?之后加入需要新的邀请。",
63003
+ "companyInvite.cancel": "取消",
63004
+ "companyInvite.loading": "正在检查邀请…",
63005
+ "companyInvite.acceptLabel": "接受{name}的邀请",
63006
+ "companyInvite.declineLabel": "拒绝{name}的邀请",
63007
+ "companyInvite.declineConfirmLabel": "确认拒绝{name}的邀请",
63008
+ "companyInvite.refreshFailed": "无法刷新邀请,请重新检查。"
62494
63009
  };
62495
63010
  const usageEntryZh = {
62496
63011
  ...selfHostConnectZh,
@@ -62630,7 +63145,7 @@ const usageEntryZh = {
62630
63145
  "localTools.prerequisitesBody": "检查 uv、Git 和 Node.js(含 npm)的安装状态。安装后请重新检查。",
62631
63146
  "localTools.prerequisitesTitle": "基本开发工具",
62632
63147
  "onboarding.preparationComplete": "必需设置完成",
62633
- "onboarding.additionalStepsBody": "Saycode 指令、AI 账户、通知以及电脑与浏览器控制均为可选步骤。您可以立即开始,稍后再设置。",
63148
+ "onboarding.additionalStepsBody": "Saycode 指令、AI 账户和通知均为可选步骤。您可以立即开始,稍后再设置。",
62634
63149
  "onboarding.additionalStep": "附加步骤",
62635
63150
  "onboarding.requiredSettings": "必需设置",
62636
63151
  "onboarding.additionalSettings": "附加设置 — 可选",
@@ -62713,6 +63228,11 @@ const projectRelocationZH = {
62713
63228
  "sessionCreation.workspaceNotSet": "此机器的工作区路径尚未设置。请先设置机器工作区。"
62714
63229
  };
62715
63230
  const chatSchedulesZh = {
63231
+ "chatSchedules.chatTitle": "此对话的预约",
63232
+ "chatSchedules.sharedHint": "您也可以在预约菜单中查看和管理相同的预约。",
63233
+ "chatSchedules.allSchedules": "查看所有预约",
63234
+ "chatSchedules.advanced": "高级自动化设置",
63235
+ "chatSchedules.advancedHint": "设置 AI、执行条件和脚本自动化。AI 预约与上方列表共享,脚本自动化是另一种执行类型。",
62716
63236
  "chatSchedules.olderReplies": "较早的回答",
62717
63237
  "chatSchedules.newerReplies": "较新的回答",
62718
63238
  "chatSchedules.limitedReplies": "当前仅显示本次运行的部分回答。请打开对话查看完整回答。",
@@ -63158,7 +63678,12 @@ const machineUsageZh = {
63158
63678
  "machineUsage.autoSwitched": "已自动切换 {provider}:{from} → {to}",
63159
63679
  "machineUsage.pinned": "已固定",
63160
63680
  "machineUsage.codexAccounts": "Codex 账户",
63161
- "machineUsage.usageStale": "正在刷新用量"
63681
+ "machineUsage.usageStale": "正在刷新用量",
63682
+ "machineUsage.autoRotation": "自动轮换账户",
63683
+ "machineUsage.autoRotationOn": "接近限额时自动切换到其他账户。",
63684
+ "machineUsage.autoRotationOff": "继续使用所选账户。",
63685
+ "machineUsage.autoRotationPaused": "你手动选择了账户,已关闭自动轮换。",
63686
+ "machineUsage.autoRotationFailed": "无法更改自动轮换,请重试。"
63162
63687
  };
63163
63688
  const machineResourceMetricsZh = {
63164
63689
  "machineResourceMetrics.title": "会话机器的 CPU/内存",
@@ -63573,7 +64098,7 @@ const outboundWebhookZh = {
63573
64098
  };
63574
64099
  const messengerChannelsZh = {
63575
64100
  "settings.messengerChannels.title": "即时通讯渠道",
63576
- "settings.messengerChannels.privacyNotice": "仅会将请求的状态和最终答复发送到已连接的即时通讯工具 — 绝不发送原始工具输出、历史记录或附件。即时通讯传输不在 Saycode 端到端加密范围内。",
64101
+ "settings.messengerChannels.privacyNotice": "会将请求的状态和最终答复发送到已连接的即时通讯工具;在 Slack 中,还会发送代理在该答复中指定的项目文件夹内的文件。绝不发送原始工具输出或历史记录。即时通讯传输不在 Saycode 端到端加密范围内。",
63577
64102
  "settings.messengerChannels.alwaysOnTitle": "退出应用后保持连接",
63578
64103
  "settings.messengerChannels.alwaysOnHelp": "退出 Saycode 后仍可接收消息并继续工作。电脑必须保持唤醒并连接互联网。",
63579
64104
  "settings.messengerChannels.alwaysOnBlocked": "不可用:此设备无法安全存储机器人凭据,因此 Desktop 退出后将无法接收。",
@@ -63731,7 +64256,7 @@ const messengerQuickSetupZh = {
63731
64256
  "settings.messengerChannels.quickSlack.openInSlack": "在 Slack 中打开",
63732
64257
  "settings.messengerChannels.quickSlack.waiting": "在 Slack 中发送后,连接请求会显示在这里。允许即可完成。",
63733
64258
  "settings.messengerChannels.quickSlack.pairedWith": "已连接 {name} 的 Slack",
63734
- "settings.messengerChannels.quickSlack.connectedHelp": "在发送连接码的私信中直接和机器人对话即可。提及机器人后发送 /help 可查看命令。",
64259
+ "settings.messengerChannels.quickSlack.connectedHelp": "在发送连接码的私信中直接和机器人对话即可。在已邀请机器人的频道中提及它,它会在该消息的话题中回复(频道成员可见)。提及机器人后发送 /help 可查看命令。",
63735
64260
  "settings.messengerChannels.quickDiscord.title": "连接 Discord",
63736
64261
  "settings.messengerChannels.quickDiscord.subtitle": "在 Discord 中直接把工作交给 Saycode。两步即可完成。",
63737
64262
  "settings.messengerChannels.quickDiscord.installTitle": "Discord 功能尚未开启",
@@ -64036,6 +64561,9 @@ const extensionPanelZh = {
64036
64561
  const extensionInstallZh = {
64037
64562
  "extensions.installOfficial": "安装扩展",
64038
64563
  "extensions.installNamedOfficial": "安装 {name}",
64564
+ "extensions.officialUpdate": "官方更新到 {version}",
64565
+ "extensions.updateSaved": "新版本已安装,但无法确认后续处理已完成。请重新检查扩展状态。",
64566
+ "extensions.projectToolHint": "在项目对话右侧工作面板的更多(⋮)菜单中打开:{tools}",
64039
64567
  "extensions.officialName.artifactPublisher": "公开链接扩展",
64040
64568
  "extensions.officialName.pluginManager": "插件管理扩展",
64041
64569
  "extensions.officialName.projectTemplates": "项目模板扩展",
@@ -64064,25 +64592,26 @@ const extensionInstallZh = {
64064
64592
  "artifactPublication.error.network": "无法连接服务器。请检查网络连接。",
64065
64593
  "artifactPublication.error.unauthenticated": "登录会话已过期。请重新登录后再试。",
64066
64594
  "artifactPublication.error.notFound": "找不到文件。请确认文件已保存。",
64595
+ "artifactPublication.error.notFoundInFolder": "在工作文件夹 {folder} 中找不到 {file}。如果文件是在其他 worktree 中创建的,请将其移到此文件夹后重试。",
64067
64596
  "artifactPublication.error.tooLarge": "文件过大。公开的 HTML 文件必须小于等于 10MB。",
64068
64597
  "artifactPublication.error.rateLimited": "公开链接请求过多。请稍后重试。",
64069
64598
  "artifactPublication.error.rejected": "服务器拒绝了此文件的公开请求。",
64070
64599
  "artifactPublication.error.serverError": "服务器发生错误。请稍后重试。",
64071
64600
  "artifactPublication.error.extensionUnavailable": "扩展已禁用或缺少公开权限。请在设置中检查 Artifact Publisher。",
64072
- "artifactPublication.error.invalidResponse": "无法校验服务器响应,已中止。"
64601
+ "artifactPublication.error.invalidResponse": "无法校验服务器响应,已中止。",
64602
+ "extensions.permission.filePreviewsRead": "读取在应用中打开的文件的预览内容。源路径和凭据由 Saycode Core 管理。"
64073
64603
  };
64074
64604
  const artifactLibraryTranslations$1 = {
64075
64605
  "artifactLibrary.title": "成果汇总",
64076
64606
  "artifactLibrary.scope": "范围",
64077
64607
  "artifactLibrary.conversation": "当前对话",
64078
64608
  "artifactLibrary.project": "当前项目",
64079
- "artifactLibrary.all": "所有项目",
64080
64609
  "artifactLibrary.search": "搜索文件、项目、对话和路径",
64081
- "artifactLibrary.hint": "汇总对话中的文档和预览。打开原始文件。",
64082
64610
  "artifactLibrary.loading": "正在查找成果…",
64083
64611
  "artifactLibrary.error": "部分对话加载失败。请重试。",
64084
64612
  "artifactLibrary.empty": "暂无成果。",
64085
64613
  "artifactLibrary.noMatches": "没有匹配的成果。",
64614
+ "artifactLibrary.readUnsupported": "此电脑不支持打开工作成果原文件。目前此功能支持 macOS 工作电脑。",
64086
64615
  "artifactLibrary.retry": "重试"
64087
64616
  };
64088
64617
  const personalChatsLandingZh = {
@@ -64946,9 +65475,15 @@ const sidebarUpdateZh = {
64946
65475
  "sidebarUpdate.completedBody": "Saycode 已更新至 v{version}。",
64947
65476
  "sidebarUpdate.installing": "正在应用更新…",
64948
65477
  "sidebarUpdate.update": "更新",
64949
- "sidebarUpdate.restartNotice": "重启前将保存对话状态。",
65478
+ "sidebarUpdate.restartNotice": "重启前将保存对话状态。安装完成后 Saycode 会自动重新打开。",
64950
65479
  "sidebarUpdate.minimize": "最小化",
64951
- "sidebarUpdate.releaseNotes": "Release notes"
65480
+ "sidebarUpdate.releaseNotes": "Release notes",
65481
+ "sidebarUpdate.relaunchWaitTitle": "正在安装更新",
65482
+ "sidebarUpdate.relaunchWaitBody": "安装完成后 Saycode v{version} 会自动打开。关闭此窗口不会中断安装。",
65483
+ "sidebarUpdate.relaunchTimeoutTitle": "更新未能完成安装",
65484
+ "sidebarUpdate.relaunchTimeoutBody": "可以用当前版本 (v{version}) 打开,并在应用中重新获取更新。",
65485
+ "sidebarUpdate.relaunchOpenCurrent": "用当前版本打开",
65486
+ "sidebarUpdate.relaunchQuit": "退出"
64952
65487
  };
64953
65488
  const globalPauseZh = {
64954
65489
  "settings.globalPause.enable": "全局暂停(阻止新的自动操作)",
@@ -66273,7 +66808,6 @@ const onboardingZh = {
66273
66808
  "onboarding.page.addProject": "添加第一个项目",
66274
66809
  "onboarding.page.configured": "已设置",
66275
66810
  "onboarding.page.optional": "可选",
66276
- "onboarding.page.computerReveal": "内部测试:显示电脑和浏览器控制",
66277
66811
  "onboarding.page.testNotification": "发送测试通知",
66278
66812
  "onboarding.page.testNotificationSending": "正在发送测试通知…",
66279
66813
  "onboarding.page.testNotificationSuccess": "测试通知已发送。",
@@ -66448,6 +66982,7 @@ const zh = {
66448
66982
  "aiModelPolicy.machineChanged": "所选电脑不可用,已停止创建对话。请检查电脑后重新发送。",
66449
66983
  "aiModelPolicy.policyChanged": "允许模型的策略已更改,已停止创建对话。请检查问题和附件后重新发送。",
66450
66984
  "aiModelPolicy.noModels": "此计算机没有可选择的获准模型。",
66985
+ ...personalAiZh,
66451
66986
  ...conversationTabsZh,
66452
66987
  ...questionComposerZh,
66453
66988
  ...aiRecoveryZh,
@@ -68672,6 +69207,9 @@ const zh = {
68672
69207
  "projectGitView.branchRename": "重命名分支",
68673
69208
  "projectGitView.branchDelete": "删除分支",
68674
69209
  "terminal.connectTimeout": "终端连接超时。",
69210
+ "terminal.strictMachineUnsupported": "安全强化(strict)机器暂不支持远程终端。请直接在该机器上使用终端。",
69211
+ "terminal.machineControlUnknown": "该机器的 CLI 未报告其安全模式,因此无法打开远程终端。请在该机器上更新 happy CLI。",
69212
+ "terminal.machineKeyUnavailable": "无法打开该机器的密钥,因此未打开远程终端。请解锁账户密钥后重试。",
68675
69213
  "terminal.connectFailed": "无法连接到终端。",
68676
69214
  "terminal.cwdPrepFailed": "无法准备远程终端的工作目录。",
68677
69215
  "terminal.restoredOutputDivider": "上一次会话输出",
@@ -69206,6 +69744,52 @@ const conversationTabsJa = {
69206
69744
  "workspaceHost.closeConversationHint": "タブだけを閉じます。会話の履歴は残ります。",
69207
69745
  "workspaceHost.newConversation": "新しい会話を開く"
69208
69746
  };
69747
+ const personalAiJa = {
69748
+ "aiConnection.sectionTitle": "接続済みAIを確認",
69749
+ "aiConnection.ai": "AI",
69750
+ "aiConnection.pc": "PC",
69751
+ "aiConnection.selectPc": "PCを選択",
69752
+ "aiConnection.pcUnknown": "確認できません",
69753
+ "aiConnection.selectAi": "AIを選択",
69754
+ "aiConnection.chooseAiHint": "AIを選ぶと、このPCの接続状態を確認できます。",
69755
+ "personalAi.settingsTitle": "自分のAIアカウント",
69756
+ "personalAi.settingsDescription": "お使いのChatGPTまたはClaudeのアカウントを接続してください。",
69757
+ "personalAi.title": "個人AIアカウントを接続",
69758
+ "personalAi.imageAlt": "Webアカウントとデスクトップの会話画面を接続する図",
69759
+ "personalAi.intro": "ChatGPTまたはClaudeのアカウントを接続して\nSayCodeでAIと会話できます。",
69760
+ "personalAi.note": "お使いのAIアカウントでログインしてください。",
69761
+ "personalAi.later": "あとで",
69762
+ "personalAi.connect": "自分のAIアカウントを接続",
69763
+ "personalAi.choose": "接続するAIを選んでください",
69764
+ "personalAi.chooseDescription": "選んだサービスのWebサイトでログインします。",
69765
+ "personalAi.withProvider": "{provider}で接続",
69766
+ "personalAi.back": "戻る",
69767
+ "personalAi.providerTitle": "{provider}アカウントを接続",
69768
+ "personalAi.waiting": "ブラウザでログインを完了してください。",
69769
+ "personalAi.approval": "ログインと接続の承認が完了すると\n接続状態を自動で確認します。",
69770
+ "personalAi.cancel": "キャンセル",
69771
+ "personalAi.reopen": "ブラウザを再度開く",
69772
+ "personalAi.expired": "接続をまだ確認できませんでした。",
69773
+ "personalAi.retryDescription": "再接続してブラウザで\nログインを完了してください。",
69774
+ "personalAi.retry": "再接続",
69775
+ "personalAi.connected": "{provider}アカウントを接続しました。",
69776
+ "personalAi.ready": "このコンピューターで使用できます。",
69777
+ "personalAi.done": "完了",
69778
+ "personalAi.localOnly": "個人AIアカウントはこのコンピューターで接続できます。作業PCにこのコンピューターを選んでから再接続してください。",
69779
+ "personalAi.unavailable": "このコンピューターの接続とAIツールのインストールを確認して、再度開いてください。",
69780
+ "aiConnection.loginLinkFailed": "ログインリンクを開けませんでした。ターミナルを表示してリンクを直接選択してください。",
69781
+ "aiConnection.openLoginLink": "ログインリンクを開く",
69782
+ "aiConnection.loginPreparing": "ログインを準備しています…",
69783
+ "aiConnection.codeLabel": "ブラウザでログインした後に表示される認証コードを貼り付けてください。",
69784
+ "aiConnection.codeSend": "コードを送信",
69785
+ "aiConnection.loginFailed": "ログインできませんでした。認証コードを確認して、もう一度お試しください。",
69786
+ "aiConnection.loginRetry": "再試行",
69787
+ "aiConnection.loginTerminalFailed": "ログイン用ターミナルを開けませんでした。下のターミナルから開き直してください。",
69788
+ "aiConnection.loginTerminalClosed": "ログイン用ターミナルが閉じました。下のターミナルから開き直してください。",
69789
+ "aiConnection.loginLinkSlow": "ログインリンクがまだ表示されていません。下のターミナルで進行状況を確認してください。",
69790
+ "aiConnection.codeSent": "認証コードを送信しました。ログインが終わったらログイン完了を押してください。",
69791
+ "aiConnection.showTerminal": "ターミナルを表示"
69792
+ };
69209
69793
  const questionComposerJa = {
69210
69794
  "askUser.skip": "スキップ",
69211
69795
  "askUser.skipAnswer": "ユーザーはこの質問への回答をスキップしました。",
@@ -69427,6 +70011,12 @@ const projectSettingsJa = {
69427
70011
  "projectSettings.diagnostics": "開発情報"
69428
70012
  };
69429
70013
  const aiRecoveryJa = {
70014
+ "aiRecovery.capacityTitle": "モデル混雑時の再試行",
70015
+ "aiRecovery.capacitySummary": "現在リクエストが集中しています。しばらくしてからもう一度お試しください。",
70016
+ "aiRecovery.capacityCountdown": "{seconds}秒後に再試行 ({attempt}/3)",
70017
+ "aiRecovery.capacityCancel": "キャンセル",
70018
+ "aiRecovery.capacityExhausted": "自動再試行が終了しました。後でもう一度試すか、入力欄でモデルを変更してください。",
70019
+ "aiRecovery.capacityModelHint": "入力欄で別のモデルに変更できます。",
69430
70020
  "aiRecovery.modelUnsupported": "このAIでは選択したモデルを使用できません。入力欄でモデルを変更してから再試行してください。",
69431
70021
  "aiRecovery.accessSummary": "このアカウントではClaudeを使用できません。",
69432
70022
  "aiRecovery.connectAccount": "AIに再接続",
@@ -69544,6 +70134,8 @@ const managedClaudeJa = {
69544
70134
  "managedClaude.loading": "AIに接続中…"
69545
70135
  };
69546
70136
  const workJa = {
70137
+ "work.mediaChanged": "元のファイルが変更されました。もう一度開いてください。",
70138
+ "work.mediaUnsupported": "このコンピューターではメディアストリーミングを利用できません。元のファイルを保存して開いてください。",
69547
70139
  "work.homeTitle": "どんな作業をお手伝いしましょうか?",
69548
70140
  "work.new": "新しいchatを開始",
69549
70141
  "work.archived": "アーカイブ済みのchat",
@@ -69599,7 +70191,8 @@ const workJa = {
69599
70191
  "work.sourceNotice.denied": "ファイルへのアクセス権がありません。権限を確認してください。",
69600
70192
  "work.sourceNotice.unavailable": "ファイルを確認できませんでした。再試行してください。",
69601
70193
  "work.saveCheck": "保存ファイルを再確認",
69602
- "work.checkingFile": "確認中…"
70194
+ "work.checkingFile": "確認中…",
70195
+ "work.mediaViewerNeeded": "メディアプレビュー拡張機能をインストールし、選択したファイルの読み取りを許可してください。"
69603
70196
  };
69604
70197
  const FILE_ACCESS_TRANSLATIONS_JA = {
69605
70198
  "fileAccess.retry": "再試行",
@@ -69675,6 +70268,8 @@ const organizationInstallRecoveryJa = {
69675
70268
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "ファイルの実行またはバージョン確認ができませんでした。",
69676
70269
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "ツールの準備に失敗しました。原因はまだ確認できていません。",
69677
70270
  "organizationStart.toolDiagnostic.reason.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "インストール済みcswapは必要な認証機能に対応していません。",
70271
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_AUTH_REQUIRED": "会社のプロキシがサインイン認証を求めたため、接続できませんでした。",
70272
+ "organizationStart.toolDiagnostic.reason.TOOL_PROXY_BLOCKED": "会社のプロキシがダウンロードまたはパッケージサーバーへの接続を許可しませんでした。",
69678
70273
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "再試行するか、ガイドに従ってインストール後に再確認してください。",
69679
70274
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "表示された前提ツールを準備してから再試行してください。",
69680
70275
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "インターネット・社内ネットワーク・プロキシを確認して再試行してください。",
@@ -69687,12 +70282,55 @@ const organizationInstallRecoveryJa = {
69687
70282
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "ネットワークを確認して再試行してください。繰り返す場合は診断を共有してください。",
69688
70283
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "インストールの完全性と社内ポリシーの実行許可を確認してください。",
69689
70284
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "再試行してください。繰り返す場合は診断を共有してください。",
69690
- "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "再試行してSaycode用の認証ツールをインストールしてください。"
70285
+ "organizationStart.toolDiagnostic.action.CLAUDE_SETUP_TOKEN_RUNTIME_UNSUPPORTED": "再試行してSaycode用の認証ツールをインストールしてください。",
70286
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_AUTH_REQUIRED": "この診断をIT担当者に伝え、ダウンロードサーバーをプロキシ認証の例外にするよう依頼してください。",
70287
+ "organizationStart.toolDiagnostic.action.TOOL_PROXY_BLOCKED": "この診断をIT担当者に伝え、ダウンロードサーバーへの接続許可を依頼してください。"
70288
+ };
70289
+ const organizationAccountKeyJa = {
70290
+ "organizationStart.accountKey.title": "会話の暗号化をオンにする",
70291
+ "organizationStart.accountKey.body": "このコンピューターで組織の AI を使うには、先に会話の暗号化をオンにしてください。現在のパスワードでこのアカウントの暗号化キーを作成します。",
70292
+ "organizationStart.accountKey.row": "会話の暗号化",
70293
+ "organizationStart.accountKey.passwordLabel": "現在のパスワード",
70294
+ "organizationStart.accountKey.checking": "パスワードを確認中…",
70295
+ "organizationStart.accountKey.recoveryTitle": "復旧キーを保管してください",
70296
+ "organizationStart.accountKey.recoveryNotice": "パスワードを忘れたときに会話を開く方法です。パスワードと復旧キーの両方を失うと、暗号化された会話は開けません。この画面を閉じると復旧キーは再表示できません。",
70297
+ "organizationStart.accountKey.copy": "コピー",
70298
+ "organizationStart.accountKey.copied": "復旧キーをコピーしました。",
70299
+ "organizationStart.accountKey.save": "ファイルに保存",
70300
+ "organizationStart.accountKey.saved": "復旧キーを保存しました。",
70301
+ "organizationStart.accountKey.confirmLabel": "復旧キーをもう一度入力",
70302
+ "organizationStart.accountKey.confirmMismatch": "入力した復旧キーが上の復旧キーと一致しません。",
70303
+ "organizationStart.accountKey.enable": "会話の暗号化をオンにする",
70304
+ "organizationStart.accountKey.saving": "オンにしています…",
70305
+ "organizationStart.accountKey.renewBody": "確認の有効時間が過ぎたため、もう一度パスワードを確認します。保管した復旧キーはそのままです。",
70306
+ "organizationStart.accountKey.renew": "再確認",
70307
+ "organizationStart.accountKey.error.wrong-password": "現在のパスワードが正しくありません。",
70308
+ "organizationStart.accountKey.error.rate-limited": "試行回数が多すぎます。しばらくしてからもう一度お試しください。",
70309
+ "organizationStart.accountKey.error.already-enabled": "このアカウントはすでに会話の暗号化がオンです。ログアウトしてもう一度ログインしてください。",
70310
+ "organizationStart.accountKey.error.signed-out": "ログインの有効期限が切れました。ログアウトしてもう一度ログインしてください。",
70311
+ "organizationStart.accountKey.error.expired": "確認の有効時間が過ぎました。パスワードをもう一度入力してください。",
70312
+ "organizationStart.accountKey.error.failed": "会話の暗号化をオンにできませんでした。しばらくしてからもう一度お試しください。",
70313
+ "organizationStart.accountKey.error.recovery-mismatch": "入力した復旧キーが上の復旧キーと一致しません。",
70314
+ "organizationStart.accountKey.lockedTitle": "もう一度ログインしてください",
70315
+ "organizationStart.accountKey.lockedBody": "このアカウントは会話の暗号化がオンですが、このコンピューターではまだ開かれていません。ログアウトしてもう一度ログインすると続行します。",
70316
+ "organizationStart.accountKey.signInAgain": "もう一度ログイン",
70317
+ "organizationStart.diagnostic.reason.accountKey": "このアカウントの会話の暗号化はまだオンになっていません。",
70318
+ "organizationStart.diagnostic.action.accountKey": "開始準備で会話の暗号化をオンにしてください。",
70319
+ "organizationStart.diagnostic.reason.accountKeyLocked": "このコンピューターではアカウントの暗号化キーがまだ開かれていません。",
70320
+ "organizationStart.diagnostic.action.accountKeyLocked": "ログアウトしてもう一度ログインしてください。"
70321
+ };
70322
+ const organizationPcRegistrationJa = {
70323
+ "organizationStart.diagnostic.reason.pcNotAssigned": "このPCは、選択した組織でこのアカウントの専用PCとして割り当てられていません。",
70324
+ "organizationStart.diagnostic.action.pcNotAssigned": "選択した組織が正しいか確認し、正しければ組織の管理者にこのPCをこのアカウントの専用PCとして割り当てるよう依頼してください。",
70325
+ "organizationStart.diagnostic.reason.pcReregister": "このPCの以前の登録情報は引き続き使用できません。",
70326
+ "organizationStart.diagnostic.action.pcReregister": "組織の管理者にこのPCを新しく登録するよう依頼してください。"
69691
70327
  };
69692
70328
  const organizationStartJa = {
69693
70329
  "organizationStart.toolsDetails": "ツール準備の詳細",
69694
70330
  "organizationStart.toolsProgress": "ツール準備状況",
69695
70331
  ...organizationInstallRecoveryJa,
70332
+ ...organizationAccountKeyJa,
70333
+ ...organizationPcRegistrationJa,
69696
70334
  "organizationStart.connectingTitle": "組織に接続中",
69697
70335
  "organizationStart.connectingBody": "{name}で作業を始める準備をしています。",
69698
70336
  "organizationStart.welcome": "組織ですぐに始めましょう",
@@ -69720,6 +70358,9 @@ const organizationStartJa = {
69720
70358
  "organizationStart.aiGroupConflict": "組織のAIアカウント設定の確認が必要です。組織の管理者にお知らせください。",
69721
70359
  "organizationStart.aiChecking": "AI接続中…",
69722
70360
  "organizationStart.connectLater": "後で接続",
70361
+ "organizationStart.skip": "スキップ",
70362
+ "organizationStart.noAi.body": "{name}には組織のAIアカウントが登録されていません。スキップして自分のAIアカウントで始められます。",
70363
+ "organizationStart.noAi.status": "組織のAIアカウントなし",
69723
70364
  "organizationStart.existingConnection": "このPCを{name}に接続するには「次へ」を押してください。",
69724
70365
  "organizationStart.failed": "オンボーディングの準備",
69725
70366
  "organizationStart.recovery.title": "アプリの修復が必要です",
@@ -69826,7 +70467,32 @@ const organizationStartJa = {
69826
70467
  "organizationStart.diagnostic.reason.registrationChanged": "このPCの組織登録が変更または削除されたため、このアカウントで接続できません。",
69827
70468
  "organizationStart.diagnostic.action.registrationChanged": "組織管理者に、組織管理 → PC でこのPCの登録を確認するよう依頼してから、もう一度お試しください。",
69828
70469
  "organizationStart.diagnostic.reason.publicVerify": "共有認証を適用しましたが、AI認証の利用を確認できませんでした。",
69829
- "organizationStart.diagnostic.action.publicVerify": "組織管理者に最新の共有認証の復旧を依頼してから再接続してください。"
70470
+ "organizationStart.diagnostic.action.publicVerify": "組織管理者に最新の共有認証の復旧を依頼してから再接続してください。",
70471
+ "companyInvite.title": "組織への招待",
70472
+ "companyInvite.checkFailed": "組織への招待の確認が必要です",
70473
+ "companyInvite.body": "新しい組織への招待があります。参加する組織を選択してください。",
70474
+ "companyInvite.onboardingBody": "作業する組織を選ぶ前に、受け取った招待を確認してください。",
70475
+ "companyInvite.member": "メンバー",
70476
+ "companyInvite.admin": "管理者",
70477
+ "companyInvite.accept": "承認",
70478
+ "companyInvite.decline": "辞退",
70479
+ "companyInvite.later": "後で",
70480
+ "companyInvite.dismiss": "組織への招待を閉じる",
70481
+ "companyInvite.accepted": "組織に参加しました。",
70482
+ "companyInvite.declined": "組織への招待を辞退しました。",
70483
+ "companyInvite.failed": "招待を処理できませんでした。後でもう一度お試しください。",
70484
+ "companyInvite.reauth": "管理者権限を適用するには、もう一度ログインしてください。",
70485
+ "companyInvite.retry": "再確認",
70486
+ "companyInvite.securityRequired": "管理者の招待にはセキュリティ認証が必要です。ウェブで設定と認証を確認して再試行してください。",
70487
+ "companyInvite.expiry": "{date}まで",
70488
+ "companyInvite.declineConfirm": "辞退を確認",
70489
+ "companyInvite.declineBody": "{name}からの招待を辞退しますか?後から参加するには新しい招待が必要です。",
70490
+ "companyInvite.cancel": "キャンセル",
70491
+ "companyInvite.loading": "招待を確認中…",
70492
+ "companyInvite.acceptLabel": "{name}の招待を承認",
70493
+ "companyInvite.declineLabel": "{name}の招待を辞退",
70494
+ "companyInvite.declineConfirmLabel": "{name}の招待の辞退を確認",
70495
+ "companyInvite.refreshFailed": "招待一覧を確認できませんでした。再確認してください。"
69830
70496
  };
69831
70497
  const usageEntryJa = {
69832
70498
  ...selfHostConnectJa,
@@ -69966,7 +70632,7 @@ const usageEntryJa = {
69966
70632
  "localTools.prerequisitesBody": "uv・Git・Node.js(npm を含む)のインストール状況を確認します。インストール後に再確認してください。",
69967
70633
  "localTools.prerequisitesTitle": "基本開発ツール",
69968
70634
  "onboarding.preparationComplete": "必須設定完了",
69969
- "onboarding.additionalStepsBody": "Saycodeの指示、AIアカウント、通知、コンピューター・ブラウザー操作は任意です。すぐに開始して、後で設定できます。",
70635
+ "onboarding.additionalStepsBody": "Saycodeの指示、AIアカウント、通知は任意です。すぐに開始して、後で設定できます。",
69970
70636
  "onboarding.additionalStep": "追加ステップ",
69971
70637
  "onboarding.requiredSettings": "必須設定",
69972
70638
  "onboarding.additionalSettings": "追加設定 — 任意",
@@ -70049,6 +70715,11 @@ const projectRelocationJA = {
70049
70715
  "sessionCreation.workspaceNotSet": "このマシンのワークスペースパスが設定されていません。先にマシンのワークスペースを設定してください。"
70050
70716
  };
70051
70717
  const chatSchedulesJa = {
70718
+ "chatSchedules.chatTitle": "このチャットの予約",
70719
+ "chatSchedules.sharedHint": "予約メニューでも同じ予約を確認・管理できます。",
70720
+ "chatSchedules.allSchedules": "すべての予約を見る",
70721
+ "chatSchedules.advanced": "自動化の詳細設定",
70722
+ "chatSchedules.advancedHint": "AI・実行条件・スクリプト自動化を設定します。AI予約は上の一覧と共通で、スクリプト自動化は別の実行形式です。",
70052
70723
  "chatSchedules.olderReplies": "以前の回答",
70053
70724
  "chatSchedules.newerReplies": "新しい回答",
70054
70725
  "chatSchedules.limitedReplies": "この実行の回答を一部表示しています。全文は会話を開いて確認してください。",
@@ -70494,7 +71165,12 @@ const machineUsageJa = {
70494
71165
  "machineUsage.autoSwitched": "{provider} アカウントを自動切替しました: {from} → {to}",
70495
71166
  "machineUsage.pinned": "固定中",
70496
71167
  "machineUsage.codexAccounts": "Codex アカウント",
70497
- "machineUsage.usageStale": "使用量を更新中"
71168
+ "machineUsage.usageStale": "使用量を更新中",
71169
+ "machineUsage.autoRotation": "アカウント自動切替",
71170
+ "machineUsage.autoRotationOn": "上限に近づくと別のアカウントに自動で切り替えます。",
71171
+ "machineUsage.autoRotationOff": "選択したアカウントを使い続けます。",
71172
+ "machineUsage.autoRotationPaused": "アカウントを選択したため自動切替をオフにしました。",
71173
+ "machineUsage.autoRotationFailed": "自動切替を変更できませんでした。もう一度お試しください。"
70498
71174
  };
70499
71175
  const machineResourceMetricsJa = {
70500
71176
  "machineResourceMetrics.title": "セッションマシンのCPU/メモリ",
@@ -70909,7 +71585,7 @@ const outboundWebhookJa = {
70909
71585
  };
70910
71586
  const messengerChannelsJa = {
70911
71587
  "settings.messengerChannels.title": "メッセンジャーチャネル",
70912
- "settings.messengerChannels.privacyNotice": "接続先メッセンジャーには、リクエストの状態と最終回答のみが送信されます — raw tool output・過去の履歴・添付ファイルは送信されません。メッセンジャー区間はSaycodeのエンドツーエンド暗号化の対象外です。",
71588
+ "settings.messengerChannels.privacyNotice": "接続先メッセンジャーには、リクエストの状態と最終回答が送信されます。Slackでは、その回答でエージェントが指定したプロジェクトフォルダー内のファイルも送信されます。raw tool output・過去の履歴は送信されません。メッセンジャー区間はSaycodeのエンドツーエンド暗号化の対象外です。",
70913
71589
  "settings.messengerChannels.alwaysOnTitle": "アプリ終了後も接続を維持",
70914
71590
  "settings.messengerChannels.alwaysOnHelp": "Saycodeを終了してもメッセージを受信し、作業を続けます。コンピューターがスリープせず、インターネットに接続されている必要があります。",
70915
71591
  "settings.messengerChannels.alwaysOnBlocked": "利用できません: この端末ではボットの資格情報を安全に保存できないため、Desktop終了後は何も受信できません。",
@@ -71067,7 +71743,7 @@ const messengerQuickSetupJa = {
71067
71743
  "settings.messengerChannels.quickSlack.openInSlack": "Slack で開く",
71068
71744
  "settings.messengerChannels.quickSlack.waiting": "Slack で送信するとここに接続リクエストが表示されます。許可すれば完了です。",
71069
71745
  "settings.messengerChannels.quickSlack.pairedWith": "{name} さんの Slack と接続済み",
71070
- "settings.messengerChannels.quickSlack.connectedHelp": "コードを送った DM でボットにそのまま話しかけてください。コマンドはボットをメンションして /help で確認できます。",
71746
+ "settings.messengerChannels.quickSlack.connectedHelp": "コードを送った DM でボットにそのまま話しかけてください。ボットを招待したチャンネルでメンションすると、そのメッセージのスレッドで返信します(チャンネルのメンバーに表示されます)。コマンドはボットをメンションして /help で確認できます。",
71071
71747
  "settings.messengerChannels.quickDiscord.title": "Discord 接続",
71072
71748
  "settings.messengerChannels.quickDiscord.subtitle": "Discord から Saycode にすぐ作業を任せられます。2 ステップで完了します。",
71073
71749
  "settings.messengerChannels.quickDiscord.installTitle": "Discord 機能がまだオフです",
@@ -71372,6 +72048,9 @@ const extensionPanelJa = {
71372
72048
  const extensionInstallJa = {
71373
72049
  "extensions.installOfficial": "拡張機能をインストール",
71374
72050
  "extensions.installNamedOfficial": "{name} をインストール",
72051
+ "extensions.officialUpdate": "{version} に公式アップデート",
72052
+ "extensions.updateSaved": "新しいバージョンはインストールされましたが、仕上げを確認できませんでした。拡張機能の状態を確認してください。",
72053
+ "extensions.projectToolHint": "プロジェクトの会話で、右側の作業パネルのその他(⋮)メニューから開きます: {tools}",
71375
72054
  "extensions.officialName.artifactPublisher": "公開リンク拡張",
71376
72055
  "extensions.officialName.pluginManager": "プラグイン管理拡張",
71377
72056
  "extensions.officialName.projectTemplates": "プロジェクトテンプレート拡張",
@@ -71400,25 +72079,26 @@ const extensionInstallJa = {
71400
72079
  "artifactPublication.error.network": "サーバーに接続できません。ネットワークを確認してください。",
71401
72080
  "artifactPublication.error.unauthenticated": "ログインセッションの有効期限が切れました。再度サインインしてください。",
71402
72081
  "artifactPublication.error.notFound": "ファイルが見つかりません。保存されているか確認してください。",
72082
+ "artifactPublication.error.notFoundInFolder": "作業フォルダー {folder} に {file} が見つかりません。別の worktree で作成したファイルなら、このフォルダーに移してから再試行してください。",
71403
72083
  "artifactPublication.error.tooLarge": "ファイルが大きすぎます。公開する HTML ファイルは 10MB 以下にしてください。",
71404
72084
  "artifactPublication.error.rateLimited": "公開リンクのリクエストが多すぎます。しばらくしてから再試行してください。",
71405
72085
  "artifactPublication.error.rejected": "サーバーがこのファイルの公開リクエストを拒否しました。",
71406
72086
  "artifactPublication.error.serverError": "サーバーエラーが発生しました。しばらくしてから再試行してください。",
71407
72087
  "artifactPublication.error.extensionUnavailable": "拡張機能が無効か、公開権限がありません。設定で Artifact Publisher を確認してください。",
71408
- "artifactPublication.error.invalidResponse": "サーバーの応答を検証できなかったため中止しました。"
72088
+ "artifactPublication.error.invalidResponse": "サーバーの応答を検証できなかったため中止しました。",
72089
+ "extensions.permission.filePreviewsRead": "アプリで開いたファイルのプレビューを読み取ります。元のパスと認証情報は Saycode Core が管理します。"
71409
72090
  };
71410
72091
  const artifactLibraryTranslations = {
71411
72092
  "artifactLibrary.title": "成果物一覧",
71412
72093
  "artifactLibrary.scope": "範囲",
71413
72094
  "artifactLibrary.conversation": "この会話",
71414
72095
  "artifactLibrary.project": "このプロジェクト",
71415
- "artifactLibrary.all": "すべてのプロジェクト",
71416
72096
  "artifactLibrary.search": "ファイル・プロジェクト・会話・パスを検索",
71417
- "artifactLibrary.hint": "会話で作成した文書とプレビューです。元のファイルを開きます。",
71418
72097
  "artifactLibrary.loading": "成果物を検索中…",
71419
72098
  "artifactLibrary.error": "一部の会話を取得できませんでした。再試行してください。",
71420
72099
  "artifactLibrary.empty": "成果物はまだありません。",
71421
72100
  "artifactLibrary.noMatches": "検索結果がありません。",
72101
+ "artifactLibrary.readUnsupported": "このPCでは作業物の元ファイルを開けません。現在はmacOSの作業PCに対応しています。",
71422
72102
  "artifactLibrary.retry": "再試行"
71423
72103
  };
71424
72104
  const personalChatsLandingJa = {
@@ -72282,9 +72962,15 @@ const sidebarUpdateJa = {
72282
72962
  "sidebarUpdate.completedBody": "Saycode v{version} に更新しました。",
72283
72963
  "sidebarUpdate.installing": "更新を適用中…",
72284
72964
  "sidebarUpdate.update": "アップデート",
72285
- "sidebarUpdate.restartNotice": "再起動の前に会話の状態を保存します。",
72965
+ "sidebarUpdate.restartNotice": "再起動の前に会話の状態を保存します。インストールが終わると Saycode が自動で開きます。",
72286
72966
  "sidebarUpdate.minimize": "最小化",
72287
- "sidebarUpdate.releaseNotes": "Release notes"
72967
+ "sidebarUpdate.releaseNotes": "Release notes",
72968
+ "sidebarUpdate.relaunchWaitTitle": "アップデートをインストールしています",
72969
+ "sidebarUpdate.relaunchWaitBody": "インストールが終わると Saycode v{version} が自動で開きます。このウィンドウを閉じてもインストールは続きます。",
72970
+ "sidebarUpdate.relaunchTimeoutTitle": "アップデートのインストールを完了できませんでした",
72971
+ "sidebarUpdate.relaunchTimeoutBody": "現在のバージョン (v{version}) で開けます。アプリからもう一度アップデートを取得できます。",
72972
+ "sidebarUpdate.relaunchOpenCurrent": "現在のバージョンで開く",
72973
+ "sidebarUpdate.relaunchQuit": "終了"
72288
72974
  };
72289
72975
  const globalPauseJa = {
72290
72976
  "settings.globalPause.enable": "グローバル一時停止(新しい自動アクションを遮断)",
@@ -73609,7 +74295,6 @@ const onboardingJa = {
73609
74295
  "onboarding.page.addProject": "最初のプロジェクトを追加",
73610
74296
  "onboarding.page.configured": "設定済み",
73611
74297
  "onboarding.page.optional": "任意",
73612
- "onboarding.page.computerReveal": "内部テスト: コンピューター・ブラウザー操作を表示",
73613
74298
  "onboarding.page.testNotification": "テスト通知を送信",
73614
74299
  "onboarding.page.testNotificationSending": "テスト通知を送信中…",
73615
74300
  "onboarding.page.testNotificationSuccess": "テスト通知を送信しました。",
@@ -73784,6 +74469,7 @@ const ja = {
73784
74469
  "aiModelPolicy.machineChanged": "選択したPCに接続できないため開始を中断しました。PCを確認して再送してください。",
73785
74470
  "aiModelPolicy.policyChanged": "許可モデルのポリシーが変わったため開始を中断しました。質問と添付を確認して再送してください。",
73786
74471
  "aiModelPolicy.noModels": "このコンピューターで選択できる許可済みモデルがありません。",
74472
+ ...personalAiJa,
73787
74473
  ...conversationTabsJa,
73788
74474
  ...questionComposerJa,
73789
74475
  ...aiRecoveryJa,
@@ -76008,6 +76694,9 @@ const ja = {
76008
76694
  "projectGitView.branchRename": "Branch 名変更",
76009
76695
  "projectGitView.branchDelete": "Branch 削除",
76010
76696
  "terminal.connectTimeout": "ターミナル接続がタイムアウトしました。",
76697
+ "terminal.strictMachineUnsupported": "セキュリティ強化(strict)マシンではまだリモートターミナルを開けません。マシン上のターミナルを直接お使いください。",
76698
+ "terminal.machineControlUnknown": "このマシンの CLI がセキュリティモードを通知しないため、リモートターミナルを開けません。マシンの happy CLI を更新してください。",
76699
+ "terminal.machineKeyUnavailable": "このマシンのキーを開けないため、リモートターミナルを開きませんでした。アカウントキーのロックを解除してから再度お試しください。",
76011
76700
  "terminal.connectFailed": "ターミナルへの接続に失敗しました。",
76012
76701
  "terminal.cwdPrepFailed": "リモートターミナルの作業ディレクトリを準備できませんでした。",
76013
76702
  "terminal.restoredOutputDivider": "前回のセッション出力",
@@ -83327,6 +84016,18 @@ function contributionList(value2, path2, allowedKeys, parse2) {
83327
84016
  return parse2(entry, entryPath);
83328
84017
  });
83329
84018
  }
84019
+ function parseExtensionFileViewers(value2, apiVersion) {
84020
+ if (apiVersion < 4) fail$7("manifest.contributes.fileViewers", "requires Extension API version 4");
84021
+ return contributionList(value2, "manifest.contributes.fileViewers", ["id", "title", "extensions", "panelId"], (entry, path2) => {
84022
+ if (!Array.isArray(entry.extensions) || !entry.extensions.length || entry.extensions.length > 30 || entry.extensions.some((extension2) => typeof extension2 !== "string" || !/^[a-z0-9]{1,12}$/.test(extension2)) || new Set(entry.extensions).size !== entry.extensions.length) fail$7(`${path2}.extensions`, "expected unique lowercase file extensions");
84023
+ return { id: id$1(entry.id, `${path2}.id`), title: string(entry.title, `${path2}.title`), extensions: entry.extensions, panelId: id$1(entry.panelId, `${path2}.panelId`) };
84024
+ });
84025
+ }
84026
+ function assertExtensionFileViewers(extensionId, viewers, panels, permissions) {
84027
+ for (const viewer of viewers) {
84028
+ if (!permissions.includes("filePreviews.read") || !viewer.id.startsWith(`${extensionId}.`) || !viewer.panelId.startsWith(`${extensionId}.`) || !panels.some((panel) => panel.id === viewer.panelId)) fail$7("manifest.contributes.fileViewers", "requires filePreviews.read and an owned panel");
84029
+ }
84030
+ }
83330
84031
  function parseMachineCommands(value2, extensionId, apiVersion) {
83331
84032
  if (apiVersion < 3) fail$7("manifest.machineCommands", "requires Extension API version 3");
83332
84033
  const root = record$4(value2, "manifest.machineCommands");
@@ -84007,6 +84708,7 @@ const EXTENSION_PERMISSIONS = [
84007
84708
  "browserViewer.install",
84008
84709
  "browserViewer.installChrome",
84009
84710
  "artifacts.publishPublic",
84711
+ "filePreviews.read",
84010
84712
  // One permission per channel wire method (`src/domain/channelProtocol.ts`), deliberately not a
84011
84713
  // single adapter grant. Receiving and acking messages is transport work; reading a session,
84012
84714
  // driving it, or approving a tool call once are three larger and separately-refusable
@@ -84021,7 +84723,7 @@ const EXTENSION_PERMISSIONS = [
84021
84723
  "sessions.control",
84022
84724
  "sessions.approveOnce"
84023
84725
  ];
84024
- const SAYCODE_EXTENSION_API_VERSION = 3;
84726
+ const SAYCODE_EXTENSION_API_VERSION = 4;
84025
84727
  const SAYCODE_EXTENSION_MINIMUM_API_VERSION = 2;
84026
84728
  const MAX_EXTENSION_API_WINDOW_WIDTH = 2;
84027
84729
  const SAYCODE_EXTENSION_ENGINE_VERSION = "1.2.0";
@@ -84047,7 +84749,7 @@ const SEMVER_PATTERN$1 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)
84047
84749
  const ENGINE_RANGE_PATTERN = /^(?:\^|~|>=|<=|>|<)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
84048
84750
  const ENTRYPOINT_PATTERN = /\.(?:cjs|mjs|js)$/;
84049
84751
  const LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Z]{2})?$/;
84050
- const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["commands", "settings", "panels", "projectTemplates", "machineActions", "projectTools", "artifactActions", "channels"]);
84752
+ const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["commands", "settings", "panels", "projectTemplates", "machineActions", "projectTools", "artifactActions", "channels", "fileViewers"]);
84051
84753
  const V2_PERMISSIONS = /* @__PURE__ */ new Set([
84052
84754
  "browserViewer.open",
84053
84755
  "browserViewer.install",
@@ -84108,7 +84810,7 @@ function engineSupports(range, version2) {
84108
84810
  function parseApiVersion(value2, options) {
84109
84811
  const current2 = options.supportedApiVersion;
84110
84812
  const floor = options.minimumSupportedApiVersion ?? current2;
84111
- if (floor > current2 || current2 - floor + 1 > MAX_EXTENSION_API_WINDOW_WIDTH) {
84813
+ if (floor > current2 || current2 - floor + 1 > (current2 === 4 && floor === 2 ? 3 : MAX_EXTENSION_API_WINDOW_WIDTH)) {
84112
84814
  fail$7(
84113
84815
  "manifest.apiVersion",
84114
84816
  `invalid Desktop API support window [${floor}, ${current2}]`
@@ -84149,6 +84851,7 @@ function parseContributions(value2, apiVersion, channelApiVersion) {
84149
84851
  fail$7("manifest.contributes.artifactActions", "requires Extension API version 3");
84150
84852
  }
84151
84853
  const result2 = {};
84854
+ if (parsed2.fileViewers !== void 0) result2.fileViewers = parseExtensionFileViewers(parsed2.fileViewers, apiVersion);
84152
84855
  if (parsed2.commands !== void 0) {
84153
84856
  result2.commands = contributionList(
84154
84857
  parsed2.commands,
@@ -84300,6 +85003,7 @@ function parseExtensionManifest(value2, options) {
84300
85003
  fail$7("manifest.permissions", `unknown permission ${parsed2}`);
84301
85004
  }
84302
85005
  const typed = parsed2;
85006
+ if (typed === "filePreviews.read" && apiVersion < 4) fail$7("manifest.permissions", "filePreviews.read requires Extension API version 4");
84303
85007
  if (apiVersion < 2 && V2_PERMISSIONS.has(typed)) {
84304
85008
  fail$7(`manifest.permissions[${index}]`, "requires Extension API version 2");
84305
85009
  }
@@ -84371,6 +85075,7 @@ function parseExtensionManifest(value2, options) {
84371
85075
  );
84372
85076
  }
84373
85077
  }
85078
+ assertExtensionFileViewers(parsedId, contributes.fileViewers ?? [], contributes.panels ?? [], permissions);
84374
85079
  assertExtensionProjectToolTargets(parsedId, contributes.projectTools ?? [], contributes.commands ?? []);
84375
85080
  return {
84376
85081
  id: parsedId,
@@ -84429,15 +85134,7 @@ class OfficialExtensionInstaller {
84429
85134
  * ones are the rest of the panel.
84430
85135
  */
84431
85136
  async installable() {
84432
- let bundled = [];
84433
- try {
84434
- bundled = (await this.readCatalog()).extensions;
84435
- } catch {
84436
- bundled = [];
84437
- }
84438
- const versions = /* @__PURE__ */ new Map();
84439
- for (const entry of bundled) versions.set(entry.id, entry.version);
84440
- for (const entry of this.options.remote?.descriptors() ?? []) versions.set(entry.id, entry.version);
85137
+ const versions = await this.offeredVersions();
84441
85138
  return [...versions].filter(([id2, version2]) => !this.options.registry.get(id2) && !this.options.revocations?.isRevoked(id2, version2)).map(([id2, version2]) => ({ id: id2, version: version2 }));
84442
85139
  }
84443
85140
  async install(id2) {
@@ -84456,6 +85153,48 @@ class OfficialExtensionInstaller {
84456
85153
  }
84457
85154
  return { id: installed.id, version: installed.version, releaseUrl: source2.metadata.releaseUrl };
84458
85155
  }
85156
+ /**
85157
+ * Installed official extensions whose offered version is newer. The installable list leaves
85158
+ * installed ids out, and compatibility migration replaces only versions Desktop can no longer
85159
+ * load, so without this an installed official extension never moves past the version it came at.
85160
+ */
85161
+ async updates() {
85162
+ const versions = await this.offeredVersions();
85163
+ const retained = await this.retainedVersions();
85164
+ return [...versions].filter(([id2, version2]) => {
85165
+ const state = this.options.registry.get(id2);
85166
+ return state && isNewerVersion(version2, state.installedVersion) && !this.options.revocations?.isRevoked(id2, version2) && !retained.has(`${id2}@${version2}`);
85167
+ }).map(([id2, version2]) => ({ id: id2, version: version2 }));
85168
+ }
85169
+ /** Same shape as a local update: the new version replaces the active one, approvals reset, the old one stays for rollback. */
85170
+ async update(id2, expectedVersion) {
85171
+ const state = this.options.registry.get(id2);
85172
+ if (!state) throw new Error(`extension is not installed: ${id2}`);
85173
+ if (state.runtime === "active") throw new Error("active extension must be disabled before update");
85174
+ const source2 = await this.resolveArtifact(id2);
85175
+ if (expectedVersion && source2.metadata.version !== expectedVersion) throw new Error(`official extension offered version changed: ${id2}`);
85176
+ if (!isNewerVersion(source2.metadata.version, state.installedVersion)) throw new Error(`official extension is up to date: ${id2}`);
85177
+ if ((await this.retainedVersions()).has(`${id2}@${source2.metadata.version}`)) {
85178
+ throw new Error(`official extension ${id2}@${source2.metadata.version} is still on disk from an earlier install that was rolled back`);
85179
+ }
85180
+ const installed = await this.options.store.install(source2.archive);
85181
+ if (installed.id !== source2.metadata.id || installed.version !== source2.metadata.version) {
85182
+ await this.options.store.removeVersion(installed.id, installed.version);
85183
+ throw new Error("official extension artifact does not match its provenance");
85184
+ }
85185
+ const current2 = this.options.registry.get(id2);
85186
+ if (!current2 || current2.installedVersion !== state.installedVersion || current2.runtime === "active") {
85187
+ await this.options.store.removeVersion(installed.id, installed.version);
85188
+ throw new Error(`official extension ${id2} changed during the update`);
85189
+ }
85190
+ try {
85191
+ await this.options.registry.apply(id2, { type: "update-installed", version: installed.version });
85192
+ } catch (error) {
85193
+ await this.options.store.removeVersion(installed.id, installed.version);
85194
+ throw error;
85195
+ }
85196
+ return { id: id2, version: installed.version, releaseUrl: source2.metadata.releaseUrl };
85197
+ }
84459
85198
  async reconcileBundledCompatibility() {
84460
85199
  const installedEntries = this.options.registry.entries();
84461
85200
  if (installedEntries.length === 0) return;
@@ -84513,6 +85252,26 @@ class OfficialExtensionInstaller {
84513
85252
  if (!metadata) throw new Error(`official extension is unavailable: ${id2}`);
84514
85253
  return { archive: await this.readArchive(metadata), metadata };
84515
85254
  }
85255
+ /**
85256
+ * Versions kept on disk. One newer than the installed version is only left behind by stepping back from it
85257
+ * (quarantine rollback or a downgrade), so it is not offered again: the store refuses to reinstall over it.
85258
+ */
85259
+ async retainedVersions() {
85260
+ return new Set((await this.options.store.listInstalled()).map((entry) => `${entry.id}@${entry.version}`));
85261
+ }
85262
+ /** The version each official id would arrive at: the signed remote catalog over the bundled copy. */
85263
+ async offeredVersions() {
85264
+ let bundled = [];
85265
+ try {
85266
+ bundled = (await this.readCatalog()).extensions;
85267
+ } catch {
85268
+ bundled = [];
85269
+ }
85270
+ const versions = /* @__PURE__ */ new Map();
85271
+ for (const entry of bundled) versions.set(entry.id, entry.version);
85272
+ for (const entry of this.options.remote?.descriptors() ?? []) versions.set(entry.id, entry.version);
85273
+ return versions;
85274
+ }
84516
85275
  async readCatalog() {
84517
85276
  try {
84518
85277
  return catalog(JSON.parse(await readFile(join(this.options.resourceRoot, "catalog.v1.json"), "utf8")));
@@ -88486,6 +89245,107 @@ class ChannelAuthCustody {
88486
89245
  onWindowClosed(_windowId) {
88487
89246
  }
88488
89247
  }
89248
+ const CHANNEL_FILE_REPLY_MAX_FILES = 5;
89249
+ const CHANNEL_FILE_REPLY_MAX_BYTES = 20 * 1024 * 1024;
89250
+ const MARKER = /^\s*MEDIA:(.*)$/;
89251
+ const FENCE = /^\s*(```|~~~)/;
89252
+ function unwrap(value2) {
89253
+ const trimmed = value2.trim();
89254
+ const quoted = /^(`+|"|'|<)(.*?)(`+|"|'|>)$/.exec(trimmed);
89255
+ return quoted ? quoted[2].trim() : trimmed;
89256
+ }
89257
+ function extractChannelFileMarkers(text2) {
89258
+ const kept = [];
89259
+ const paths = [];
89260
+ let fenced = false;
89261
+ for (const line2 of text2.split(/\r?\n/)) {
89262
+ if (FENCE.test(line2)) fenced = !fenced;
89263
+ const marker = fenced ? null : MARKER.exec(line2);
89264
+ if (!marker) {
89265
+ kept.push(line2);
89266
+ continue;
89267
+ }
89268
+ const path2 = unwrap(marker[1]);
89269
+ if (path2 && !paths.includes(path2)) paths.push(path2);
89270
+ }
89271
+ if (paths.length === 0) return { text: text2, paths };
89272
+ return { text: kept.join("\n").replace(/\s+$/, ""), paths: Object.freeze(paths) };
89273
+ }
89274
+ const WINDOWS_ROOT = /^[A-Za-z]:[\\/]/;
89275
+ function isHiddenOrSecret(segment) {
89276
+ return segment.startsWith(".") || /\.pem$/i.test(segment) || /^id_/i.test(segment);
89277
+ }
89278
+ function vetChannelFilePath(path2, workingDirectory) {
89279
+ if (!path2.trim() || /[\u0000-\u001f\u007f]/.test(path2)) return { ok: false, reason: "invalid" };
89280
+ const windows = WINDOWS_ROOT.test(workingDirectory);
89281
+ const flavor = windows ? win32 : posix;
89282
+ if (!workingDirectory || !flavor.isAbsolute(workingDirectory)) return { ok: false, reason: "invalid" };
89283
+ const root = flavor.resolve(workingDirectory);
89284
+ const absolutePath = flavor.resolve(root, path2);
89285
+ const fold = (value2) => windows ? value2.toLowerCase() : value2;
89286
+ const relative2 = flavor.relative(fold(root), fold(absolutePath));
89287
+ if (!relative2 || relative2.startsWith("..") || flavor.isAbsolute(relative2)) {
89288
+ return { ok: false, reason: "outside-workspace" };
89289
+ }
89290
+ const segments2 = absolutePath.slice(root.length).split(flavor.sep).filter(Boolean);
89291
+ if (segments2.some(isHiddenOrSecret)) return { ok: false, reason: "hidden-or-secret" };
89292
+ return { ok: true, absolutePath, root, segments: Object.freeze(segments2) };
89293
+ }
89294
+ function validDiscoveryPath(path2) {
89295
+ return typeof path2 === "string" && path2.length > 0 && path2.length < 4096 && !/[\\\0]/.test(path2) && !path2.startsWith("/") && path2.split("/").every((part) => part !== "" && part !== "." && part !== "..");
89296
+ }
89297
+ async function request(access2, params, call2 = callDesktopMachineRpc) {
89298
+ if (!access2.machine.online) throw new Error("offline");
89299
+ const result2 = await call2(access2.auth, access2.machine, "file-discovery", { version: 1, root: access2.root, ...params });
89300
+ if (!result2 || result2.version !== 1 || result2.success !== true) throw new Error(typeof result2?.error === "string" ? result2.error : "unsupported");
89301
+ return result2;
89302
+ }
89303
+ async function readDiscoveryFile(access2, path2, options = {}) {
89304
+ if (!validDiscoveryPath(path2)) throw new Error("invalid-path");
89305
+ const maxBytes = options.maxBytes ?? 25 * 1024 * 1024;
89306
+ const chunks = [];
89307
+ let offset = 0;
89308
+ let identity;
89309
+ while (offset <= maxBytes) {
89310
+ const result2 = await request(access2, { operation: "read", path: path2, offset, length: 262144, ...identity ? { identity } : {} }, options.call);
89311
+ if (typeof result2.content !== "string" || result2.content.length > 349528 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(result2.content) || typeof result2.identity !== "string" || identity !== void 0 && identity !== result2.identity || result2.offset !== offset || typeof result2.eof !== "boolean" || !Number.isSafeInteger(result2.size) || result2.size < 0 || result2.size > 25 * 1024 * 1024) throw new Error("invalid-response");
89312
+ if (result2.size > maxBytes) throw new Error("too-large");
89313
+ identity = result2.identity;
89314
+ const chunk = Buffer$1.from(result2.content, "base64");
89315
+ offset += chunk.length;
89316
+ chunks.push(chunk);
89317
+ if (result2.eof === true && offset === result2.size) return Buffer$1.concat(chunks).toString("base64");
89318
+ if (!chunk.length || offset >= result2.size) throw new Error("invalid-response");
89319
+ }
89320
+ throw new Error("too-large");
89321
+ }
89322
+ async function readChannelSessionFile(input) {
89323
+ const { auth, machine, workingDirectory } = input.location;
89324
+ const verdict = vetChannelFilePath(input.path, workingDirectory);
89325
+ if (!verdict.ok) return { ok: false, reason: verdict.reason };
89326
+ if (!machine.online) return { ok: false, reason: "machine-offline" };
89327
+ try {
89328
+ const base642 = await readDiscoveryFile({ auth, machine, root: verdict.root }, verdict.segments.join("/"), {
89329
+ call: input.callMachineRpc,
89330
+ maxBytes: input.maxBytes ?? CHANNEL_FILE_REPLY_MAX_BYTES
89331
+ });
89332
+ return { ok: true, fileName: verdict.segments.at(-1), bytes: new Uint8Array(Buffer.from(base642, "base64")) };
89333
+ } catch (error) {
89334
+ const code = error instanceof Error ? error.message : "";
89335
+ return { ok: false, reason: code === "too-large" ? "too-large" : code === "not-file" ? "not-a-file" : "unreadable" };
89336
+ }
89337
+ }
89338
+ function createChannelSessionFileLocator(deps) {
89339
+ return async (principalKey, sessionId) => {
89340
+ const auth = deps.authFor(principalKey);
89341
+ if (!auth) return null;
89342
+ const session = await deps.operations.primeSession(auth, sessionId).catch(() => null);
89343
+ if (!session?.machineId || !session.path) return null;
89344
+ const machines2 = await deps.operations.listMachines(auth).catch(() => []);
89345
+ const machine = machines2.find((candidate) => candidate.id === session.machineId);
89346
+ return machine ? { auth, machine, workingDirectory: session.path } : null;
89347
+ };
89348
+ }
88489
89349
  class ChannelCapabilityError extends Error {
88490
89350
  constructor(code, message) {
88491
89351
  super(`${code}: ${message}`);
@@ -89076,6 +89936,25 @@ function channelReconnectDelayMs(attempt2, options) {
89076
89936
  const exponential = Math.min(options.maxMs, options.baseMs * 2 ** Math.max(0, attempt2 - 1));
89077
89937
  return Math.floor(exponential * (0.5 + 0.5 * options.jitter()));
89078
89938
  }
89939
+ const SLACK_FILE_UPLOAD_ORIGINS = Object.freeze(["https://files.slack.com"]);
89940
+ const SLACK_FILE_UPLOAD_OPERATIONS = Object.freeze({
89941
+ filesGetUploadURLExternal: Object.freeze({
89942
+ method: "POST",
89943
+ direction: "core-internal",
89944
+ pathTemplate: "/api/files.getUploadURLExternal",
89945
+ credentialPlacement: "authorizationBearer",
89946
+ parameterPlacement: "query",
89947
+ allowedParams: Object.freeze(["filename", "length"])
89948
+ }),
89949
+ filesCompleteUploadExternal: Object.freeze({
89950
+ method: "POST",
89951
+ direction: "outbound",
89952
+ pathTemplate: "/api/files.completeUploadExternal",
89953
+ credentialPlacement: "authorizationBearer",
89954
+ parameterPlacement: "query",
89955
+ allowedParams: Object.freeze(["files", "channel_id", "thread_ts"])
89956
+ })
89957
+ });
89079
89958
  const MAX_SELECTOR_DEPTH = 6;
89080
89959
  const MAX_EVENTS_PER_PAYLOAD = 100;
89081
89960
  const TELEGRAM = Object.freeze({
@@ -89247,7 +90126,9 @@ const SLACK$1 = Object.freeze({
89247
90126
  credentialPlacement: "authorizationBearer",
89248
90127
  parameterPlacement: "jsonBody",
89249
90128
  allowedParams: Object.freeze(["channel", "ts", "text"])
89250
- }
90129
+ },
90130
+ // Files attached to a final answer (specs/channel-file-replies). Core-only, like the two above.
90131
+ ...SLACK_FILE_UPLOAD_OPERATIONS
89251
90132
  }),
89252
90133
  inbound: Object.freeze({
89253
90134
  eventsPath: Object.freeze(["events"]),
@@ -89405,64 +90286,7 @@ function channelProviderContract(provider2) {
89405
90286
  if (!contract) throw new Error(`no channel contract for provider ${provider2}`);
89406
90287
  return contract;
89407
90288
  }
89408
- async function withdrawIntakeDurably(cancelled, connectionKeys, deps) {
89409
- const undurable = await journalCancelledIntake(cancelled, deps);
89410
- if (undurable.length === 0) return EMPTY_WITHDRAWAL;
89411
- const unsafeToReopen = [];
89412
- for (const key of connectionKeys) {
89413
- try {
89414
- await deps.revokeGeneration(key);
89415
- } catch {
89416
- unsafeToReopen.push(key);
89417
- deps.onDiagnostic?.({
89418
- code: "INTAKE_WITHDRAWAL_NOT_ENFORCEABLE",
89419
- message: "a withdrawal was neither recorded nor fenced by a generation move"
89420
- });
89421
- }
89422
- }
89423
- return Object.freeze({ unsafeToReopen: Object.freeze(unsafeToReopen) });
89424
- }
89425
- const EMPTY_WITHDRAWAL = Object.freeze({
89426
- unsafeToReopen: Object.freeze([])
89427
- });
89428
- async function journalCancelledIntake(entries, deps) {
89429
- const undurable = [];
89430
- for (const entry of entries) {
89431
- try {
89432
- await deps.journal.record(entry);
89433
- } catch {
89434
- undurable.push(entry.id);
89435
- deps.onDiagnostic?.({
89436
- code: "INTAKE_CANCELLATION_NOT_DURABLE",
89437
- message: "a cancelled request could not be recorded"
89438
- });
89439
- }
89440
- }
89441
- return Object.freeze(undurable);
89442
- }
89443
- function intakeStateForTurnOutcome(outcome) {
89444
- if (outcome.kind === "final") return "executed";
89445
- if (outcome.status === "cancelled") return "cancelled";
89446
- if (outcome.status === "no-final-answer") return "executed";
89447
- return "failed";
89448
- }
89449
- async function settleIntakeEntry(queue, requestId, state, deps) {
89450
- let settled;
89451
- try {
89452
- settled = queue.transition(requestId, state);
89453
- } catch {
89454
- return;
89455
- }
89456
- try {
89457
- await deps.journal.record(settled);
89458
- } catch {
89459
- deps.onDiagnostic?.({
89460
- code: "INTAKE_TERMINAL_NOT_DURABLE",
89461
- message: "a finished request could not be recorded"
89462
- });
89463
- }
89464
- }
89465
- function readString$1(source2, path2) {
90289
+ function readString$2(source2, path2) {
89466
90290
  let current2 = source2;
89467
90291
  for (const key of path2) {
89468
90292
  if (!current2 || typeof current2 !== "object" || Array.isArray(current2)) return null;
@@ -89497,12 +90321,14 @@ function withText(event, text2) {
89497
90321
  return text2 === event.text ? event : { ...event, text: text2 };
89498
90322
  }
89499
90323
  function refineSlack(entry, event, context) {
89500
- const channelType = readString$1(entry, ["payload", "event", "channel_type"]);
90324
+ const channelType = readString$2(entry, ["payload", "event", "channel_type"]);
89501
90325
  const isDirect = channelType === "im" || channelType === "mpim";
89502
90326
  const rootable = !event.action && !isDirect;
89503
- const threadRef = event.conversation.threadRef ?? (rootable ? readString$1(entry, ["payload", "event", "ts"]) : null);
90327
+ const threadRef = event.conversation.threadRef ?? (rootable ? readString$2(entry, ["payload", "event", "ts"]) : null);
89504
90328
  const text2 = canonicalizeVerifiedMention(event.text, context.verifiedBotRef);
89505
- return withText(withThread(event, threadRef), text2);
90329
+ const refined = withText(withThread(event, threadRef), text2);
90330
+ const invited = rootable && readString$2(entry, ["payload", "event", "type"]) === "app_mention" && entry?.payload?.is_ext_shared_channel === false;
90331
+ return invited ? { ...refined, invitedMention: true } : refined;
89506
90332
  }
89507
90333
  function refineDiscord(event, context) {
89508
90334
  const text2 = canonicalizeVerifiedMention(event.text, context.verifiedBotRef);
@@ -89655,7 +90481,7 @@ function readRecord(source2, key) {
89655
90481
  const value2 = source2[key];
89656
90482
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
89657
90483
  }
89658
- function readString(source2, key) {
90484
+ function readString$1(source2, key) {
89659
90485
  const value2 = source2?.[key];
89660
90486
  return typeof value2 === "string" && value2.length > 0 ? value2 : null;
89661
90487
  }
@@ -89683,7 +90509,7 @@ function normalizeDiscordInteraction(frame) {
89683
90509
  if (!d || d.type !== DISCORD_APPLICATION_COMMAND) return null;
89684
90510
  const data = readRecord(d, "data");
89685
90511
  if (!data || data.type !== DISCORD_CHAT_INPUT_DATA) return null;
89686
- const name = readString(data, "name");
90512
+ const name = readString$1(data, "name");
89687
90513
  if (!name || !Object.prototype.hasOwnProperty.call(CHANNEL_COMMAND_SCHEMA, name)) return null;
89688
90514
  const specs = CHANNEL_COMMAND_SCHEMA[name];
89689
90515
  const declared = new Set(specs.map((spec) => spec.endsWith("!") ? spec.slice(0, -1) : spec));
@@ -89693,7 +90519,7 @@ function normalizeDiscordInteraction(frame) {
89693
90519
  if (!Array.isArray(options)) return null;
89694
90520
  for (const option of options) {
89695
90521
  const entry = option && typeof option === "object" && !Array.isArray(option) ? option : null;
89696
- const optionName = readString(entry, "name");
90522
+ const optionName = readString$1(entry, "name");
89697
90523
  const value2 = entry?.value;
89698
90524
  if (!optionName || !declared.has(optionName)) return null;
89699
90525
  if (entry?.type !== DISCORD_STRING_OPTION$1 || typeof value2 !== "string") return null;
@@ -89707,9 +90533,9 @@ function normalizeDiscordInteraction(frame) {
89707
90533
  }
89708
90534
  const direct = readRecord(d, "user");
89709
90535
  const membership = readRecord(readRecord(d, "member"), "user");
89710
- if (direct && membership && readString(direct, "id") !== readString(membership, "id")) return null;
90536
+ if (direct && membership && readString$1(direct, "id") !== readString$1(membership, "id")) return null;
89711
90537
  const user = membership ?? direct;
89712
- const senderRef = readString(user, "id");
90538
+ const senderRef = readString$1(user, "id");
89713
90539
  if (!senderRef) return null;
89714
90540
  const bot = user?.bot;
89715
90541
  if (bot !== void 0 && typeof bot !== "boolean") return null;
@@ -89719,11 +90545,11 @@ function normalizeDiscordInteraction(frame) {
89719
90545
  entry: {
89720
90546
  ...frame,
89721
90547
  d: {
89722
- id: readString(d, "id"),
89723
- channel_id: readString(d, "channel_id"),
90548
+ id: readString$1(d, "id"),
90549
+ channel_id: readString$1(d, "channel_id"),
89724
90550
  author: {
89725
90551
  id: senderRef,
89726
- ...readString(user, "username") ? { username: readString(user, "username") } : {},
90552
+ ...readString$1(user, "username") ? { username: readString$1(user, "username") } : {},
89727
90553
  ...bot === void 0 ? {} : { bot }
89728
90554
  },
89729
90555
  content: text2
@@ -89755,21 +90581,21 @@ function normalizeSlackAction(frame) {
89755
90581
  if (!pressed || pressed.type !== "button") return null;
89756
90582
  const action = parseChannelApprovalActionId(pressed.action_id);
89757
90583
  if (!action) return null;
89758
- const senderRef = readString(readRecord(payload, "user"), "id");
90584
+ const senderRef = readString$1(readRecord(payload, "user"), "id");
89759
90585
  const container = readRecord(payload, "container");
89760
90586
  if (!container || container.type !== "message") return null;
89761
- const conversationRef = readString(container, "channel_id");
89762
- const stated = readString(readRecord(payload, "channel"), "id");
90587
+ const conversationRef = readString$1(container, "channel_id");
90588
+ const stated = readString$1(readRecord(payload, "channel"), "id");
89763
90589
  if (!conversationRef || stated !== null && stated !== conversationRef) return null;
89764
- const threadRef = readString(readRecord(payload, "message"), "thread_ts") ?? readString(container, "thread_ts");
89765
- const pressedAt = readString(pressed, "action_ts");
90590
+ const threadRef = readString$1(readRecord(payload, "message"), "thread_ts") ?? readString$1(container, "thread_ts");
90591
+ const pressedAt = readString$1(pressed, "action_ts");
89766
90592
  if (!senderRef || !pressedAt) return null;
89767
90593
  return {
89768
90594
  entry: {
89769
90595
  ...frame,
89770
90596
  payload: {
89771
90597
  ...payload,
89772
- team_id: readString(readRecord(payload, "team"), "id") ?? void 0,
90598
+ team_id: readString$1(readRecord(payload, "team"), "id") ?? void 0,
89773
90599
  // Rebuilt, not copied: `payload.message` describes the bot's own prompt, and carrying its
89774
90600
  // `user`/`bot_id` through would attribute the click to Saycode.
89775
90601
  event: {
@@ -89793,10 +90619,10 @@ function normalizeDiscordAction(frame, d) {
89793
90619
  if (!action) return null;
89794
90620
  const direct = readRecord(d, "user");
89795
90621
  const membership = readRecord(readRecord(d, "member"), "user");
89796
- if (direct && membership && readString(direct, "id") !== readString(membership, "id")) return null;
90622
+ if (direct && membership && readString$1(direct, "id") !== readString$1(membership, "id")) return null;
89797
90623
  const user = membership ?? direct;
89798
- const senderRef = readString(user, "id");
89799
- const conversationRef = readString(d, "channel_id");
90624
+ const senderRef = readString$1(user, "id");
90625
+ const conversationRef = readString$1(d, "channel_id");
89800
90626
  if (!senderRef || !conversationRef) return null;
89801
90627
  const bot = user?.bot;
89802
90628
  if (bot !== void 0 && typeof bot !== "boolean") return null;
@@ -89805,7 +90631,7 @@ function normalizeDiscordAction(frame, d) {
89805
90631
  ...frame,
89806
90632
  d: {
89807
90633
  // The interaction id, unique per press — a second click is its own event.
89808
- id: readString(d, "id"),
90634
+ id: readString$1(d, "id"),
89809
90635
  channel_id: conversationRef,
89810
90636
  author: { id: senderRef, ...bot === void 0 ? {} : { bot } },
89811
90637
  content: action.decision
@@ -90713,6 +91539,344 @@ function classifyProgressResult(contract, result2, onDiagnostic) {
90713
91539
  }
90714
91540
  return { ok: false, retryable };
90715
91541
  }
91542
+ async function sendChannelOutbound(message, deps) {
91543
+ const contract = channelProviderContract(deps.provider);
91544
+ const mapping = contract.outbound;
91545
+ if (message.body.length > mapping.maxTextLength) {
91546
+ deps.onDiagnostic?.({
91547
+ code: "REPLY_TOO_LONG",
91548
+ message: `the reply exceeds ${mapping.maxTextLength} characters for this provider`
91549
+ });
91550
+ return "failed";
91551
+ }
91552
+ if (!deps.isCurrent()) return "failed";
91553
+ const params = {
91554
+ [mapping.conversationParam]: message.destination.conversationRef,
91555
+ [mapping.textParam]: message.body
91556
+ };
91557
+ if (mapping.threadParam && message.destination.threadRef) {
91558
+ params[mapping.threadParam] = message.destination.threadRef;
91559
+ }
91560
+ if (mapping.replyToParam && message.destination.replyToTransportEventId) {
91561
+ params[mapping.replyToParam] = mapping.replyToIsStructured && mapping.replyToStructuredField ? { [mapping.replyToStructuredField]: message.destination.replyToTransportEventId } : message.destination.replyToTransportEventId;
91562
+ }
91563
+ if (deps.interactive && mapping.interactiveParam) {
91564
+ params[mapping.interactiveParam] = deps.interactive;
91565
+ }
91566
+ let result2;
91567
+ try {
91568
+ result2 = await deps.executor.execute(message.connectionId, {
91569
+ operation: mapping.operation,
91570
+ params,
91571
+ credentialRevision: message.connectionRevision
91572
+ }, void 0, () => {
91573
+ if (!deps.stillSending()) throw new Error("withdrawn");
91574
+ });
91575
+ } catch (error) {
91576
+ if (error?.code === "SEND_FENCED") {
91577
+ deps.onDiagnostic?.({
91578
+ code: "REPLY_WITHDRAWN",
91579
+ message: "the reply was withdrawn before it was sent"
91580
+ });
91581
+ return "failed";
91582
+ }
91583
+ deps.onDiagnostic?.({ code: "REPLY_SEND_UNCONFIRMED", message: "the reply could not be confirmed" });
91584
+ return "unknown";
91585
+ }
91586
+ const refusal = pollRefusal(contract, result2);
91587
+ if (refusal.refused) {
91588
+ deps.onDiagnostic?.({
91589
+ code: "REPLY_REFUSED",
91590
+ message: `the provider refused the reply (${refusal.reason})`
91591
+ });
91592
+ return "failed";
91593
+ }
91594
+ if (deps.onDelivered) {
91595
+ const messageId2 = readProviderMessageId(deps.provider, result2.body);
91596
+ if (messageId2) deps.onDelivered(messageId2);
91597
+ }
91598
+ return "sent";
91599
+ }
91600
+ const UPLOAD_TIMEOUT_MS = 12e4;
91601
+ function readString(body, key) {
91602
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
91603
+ const value2 = body[key];
91604
+ return typeof value2 === "string" && value2.length > 0 ? value2 : null;
91605
+ }
91606
+ async function uploadSlackFile(input) {
91607
+ const contract = channelProviderContract("slack");
91608
+ const fail2 = (code, message) => {
91609
+ input.onDiagnostic?.({ code, message });
91610
+ return "failed";
91611
+ };
91612
+ const fence = () => {
91613
+ if (!input.stillCurrent()) throw new Error("withdrawn");
91614
+ };
91615
+ if (!input.stillCurrent()) return fail2("FILE_UPLOAD_WITHDRAWN", "the connection changed before the file was sent");
91616
+ try {
91617
+ const ticket = await input.executor.execute(input.connectionId, {
91618
+ operation: "filesGetUploadURLExternal",
91619
+ params: { filename: input.fileName, length: input.bytes.byteLength },
91620
+ credentialRevision: input.connectionRevision
91621
+ }, void 0, fence);
91622
+ if (pollRefusal(contract, ticket).refused) {
91623
+ return fail2("FILE_UPLOAD_REFUSED", "Slack refused the upload (is files:write granted?)");
91624
+ }
91625
+ const uploadUrl = readString(ticket.body, "upload_url");
91626
+ const fileId = readString(ticket.body, "file_id");
91627
+ if (!uploadUrl || !fileId) return fail2("FILE_UPLOAD_REFUSED", "Slack answered without an upload URL");
91628
+ let target;
91629
+ try {
91630
+ target = assertDestinationAllowed(uploadUrl, { allowedOrigins: SLACK_FILE_UPLOAD_ORIGINS, rejectPrivateNetwork: true });
91631
+ } catch {
91632
+ return fail2("FILE_UPLOAD_ORIGIN_REFUSED", "Slack named an upload host outside the allowlist");
91633
+ }
91634
+ if (!input.stillCurrent()) return fail2("FILE_UPLOAD_WITHDRAWN", "the connection changed before the file was sent");
91635
+ const controller = new AbortController();
91636
+ const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);
91637
+ let status;
91638
+ try {
91639
+ const response2 = await (input.fetchImpl ?? fetch)(target.toString(), {
91640
+ method: "POST",
91641
+ headers: { "content-type": "application/octet-stream" },
91642
+ // A copy on its own ArrayBuffer: what `fetch` accepts as a body, whatever backed the input.
91643
+ body: new Uint8Array(input.bytes),
91644
+ redirect: "manual",
91645
+ signal: controller.signal
91646
+ });
91647
+ status = response2.status;
91648
+ void response2.body?.cancel().catch(() => {
91649
+ });
91650
+ } finally {
91651
+ clearTimeout(timer);
91652
+ }
91653
+ if (status < 200 || status >= 300) return fail2("FILE_UPLOAD_REFUSED", `the upload host answered ${status}`);
91654
+ const completed = await input.executor.execute(input.connectionId, {
91655
+ operation: "filesCompleteUploadExternal",
91656
+ params: {
91657
+ files: JSON.stringify([{ id: fileId, title: input.fileName }]),
91658
+ channel_id: input.destination.conversationRef,
91659
+ ...input.destination.threadRef ? { thread_ts: input.destination.threadRef } : {}
91660
+ },
91661
+ credentialRevision: input.connectionRevision
91662
+ }, void 0, fence);
91663
+ if (pollRefusal(contract, completed).refused) return fail2("FILE_UPLOAD_REFUSED", "Slack refused to post the file");
91664
+ return "sent";
91665
+ } catch (error) {
91666
+ if (error?.code === "SEND_FENCED") {
91667
+ return fail2("FILE_UPLOAD_WITHDRAWN", "the connection changed before the file was sent");
91668
+ }
91669
+ return fail2("FILE_UPLOAD_UNCONFIRMED", "the file upload could not be confirmed");
91670
+ }
91671
+ }
91672
+ const MAX_PENDING = 100;
91673
+ const MAX_ACTIVE_DELIVERIES = 2;
91674
+ class ChannelFileReplies {
91675
+ constructor(options) {
91676
+ this.options = options;
91677
+ this.enabled = "filesGetUploadURLExternal" in channelProviderContract(options.provider).operations;
91678
+ }
91679
+ // Shared across connections: capacity is acquired before locating or reading any file.
91680
+ static active = 0;
91681
+ static waiting = [];
91682
+ pending = /* @__PURE__ */ new Map();
91683
+ enabled;
91684
+ /** The text to queue in place of `text`. Unchanged unless it names files this provider can send. */
91685
+ prepareFinal(binding, text2) {
91686
+ if (!this.enabled) return text2;
91687
+ const extracted = extractChannelFileMarkers(text2);
91688
+ if (extracted.paths.length === 0) return text2;
91689
+ if (this.pending.size >= MAX_PENDING && !this.pending.has(binding.requestId)) {
91690
+ return [extracted.text, this.noticeText(extracted.paths.length, extracted.paths.length)].filter(Boolean).join("\n\n");
91691
+ }
91692
+ this.pending.set(binding.requestId, { binding, paths: extracted.paths });
91693
+ return extracted.text || channelText({ en: "📎 Sending the files.", ko: "📎 파일을 보내요." });
91694
+ }
91695
+ /** No text was admitted/durably recorded, so there is no attachment obligation. */
91696
+ discard(requestId) {
91697
+ this.pending.delete(requestId);
91698
+ }
91699
+ /** The answer's delivery was decided. Resolves when its files are done; callers need not wait. */
91700
+ async settled(message) {
91701
+ const pending2 = this.pending.get(message.requestId);
91702
+ if (!pending2) return;
91703
+ this.pending.delete(message.requestId);
91704
+ if (message.state !== "sent") return;
91705
+ if (ChannelFileReplies.waiting.length >= MAX_PENDING) {
91706
+ await this.attempt(() => this.notify(pending2.binding, pending2.paths.length, pending2.paths.length));
91707
+ return;
91708
+ }
91709
+ await new Promise((resolve2) => {
91710
+ ChannelFileReplies.waiting.push({ run: () => this.attempt(() => this.deliver(pending2)), resolve: resolve2 });
91711
+ ChannelFileReplies.drain();
91712
+ });
91713
+ }
91714
+ static drain() {
91715
+ while (this.active < MAX_ACTIVE_DELIVERIES && this.waiting.length > 0) {
91716
+ const job = this.waiting.shift();
91717
+ this.active += 1;
91718
+ void job.run().finally(() => {
91719
+ this.active -= 1;
91720
+ job.resolve();
91721
+ this.drain();
91722
+ });
91723
+ }
91724
+ }
91725
+ async attempt(run2) {
91726
+ try {
91727
+ await run2();
91728
+ } catch {
91729
+ this.options.onDiagnostic?.({ code: "FILE_REPLY_FAILED", message: "the files could not be delivered" });
91730
+ }
91731
+ }
91732
+ async deliver({ binding, paths }) {
91733
+ const current2 = () => this.options.isCurrent(binding.connectionRevision);
91734
+ if (!current2()) return;
91735
+ const attempted = paths.slice(0, CHANNEL_FILE_REPLY_MAX_FILES);
91736
+ let failed = paths.length - attempted.length;
91737
+ const location2 = await this.options.locate(binding.sessionId).catch(() => null);
91738
+ for (const path2 of attempted) {
91739
+ if (!current2()) return;
91740
+ if (!location2) {
91741
+ failed += 1;
91742
+ continue;
91743
+ }
91744
+ const file = await readChannelSessionFile({ location: location2, path: path2, callMachineRpc: this.options.callMachineRpc });
91745
+ if (!file.ok) {
91746
+ this.options.onDiagnostic?.({ code: "FILE_REPLY_REFUSED", message: `a named file was not sent (${file.reason})` });
91747
+ failed += 1;
91748
+ continue;
91749
+ }
91750
+ const outcome = await uploadSlackFile({
91751
+ connectionId: binding.connectionId,
91752
+ connectionRevision: binding.connectionRevision,
91753
+ destination: binding.destination,
91754
+ fileName: file.fileName,
91755
+ bytes: file.bytes,
91756
+ executor: this.options.executor,
91757
+ stillCurrent: current2,
91758
+ ...this.options.fetchImpl ? { fetchImpl: this.options.fetchImpl } : {},
91759
+ ...this.options.onDiagnostic ? { onDiagnostic: this.options.onDiagnostic } : {}
91760
+ });
91761
+ if (outcome !== "sent") failed += 1;
91762
+ }
91763
+ if (failed > 0 && current2()) await this.notify(binding, failed, paths.length);
91764
+ }
91765
+ noticeText(failed, total) {
91766
+ return channelText({
91767
+ en: `⚠️ ${failed} of ${total} file(s) could not be attached. Open the session in Saycode to get them.`,
91768
+ ko: `⚠️ 파일 ${total}개 중 ${failed}개를 첨부하지 못했어요. Saycode에서 세션을 열어 확인하세요.`
91769
+ });
91770
+ }
91771
+ /** One line, no paths: which files were refused is the session's business, not the channel's. */
91772
+ async notify(binding, failed, total) {
91773
+ if (!this.options.isCurrent(binding.connectionRevision)) return;
91774
+ const body = this.noticeText(failed, total);
91775
+ const notice = {
91776
+ id: `file-notice:${binding.requestId}`,
91777
+ sequence: 0,
91778
+ connectionId: binding.connectionId,
91779
+ connectionRevision: binding.connectionRevision,
91780
+ requestId: `file-notice:${binding.requestId}`,
91781
+ sessionId: binding.sessionId,
91782
+ destination: binding.destination,
91783
+ body,
91784
+ state: "sending",
91785
+ attempts: 0,
91786
+ createdAt: this.options.now(),
91787
+ kind: "status"
91788
+ };
91789
+ const current2 = () => this.options.isCurrent(binding.connectionRevision);
91790
+ await sendChannelOutbound(notice, {
91791
+ provider: this.options.provider,
91792
+ executor: this.options.executor,
91793
+ isCurrent: current2,
91794
+ stillSending: current2,
91795
+ ...this.options.onDiagnostic ? { onDiagnostic: this.options.onDiagnostic } : {}
91796
+ });
91797
+ }
91798
+ }
91799
+ function channelFileReplyHooks(source2, input) {
91800
+ if (!source2) return {};
91801
+ const { principalKey, ...options } = input;
91802
+ const files = new ChannelFileReplies({
91803
+ ...options,
91804
+ locate: (sessionId) => source2.locate(principalKey, sessionId),
91805
+ callMachineRpc: source2.callMachineRpc
91806
+ });
91807
+ return {
91808
+ prepareFinal: (binding, text2) => files.prepareFinal(binding, text2),
91809
+ onReplySettled: (message) => {
91810
+ void files.settled(message);
91811
+ },
91812
+ onReplyDiscarded: (requestId) => files.discard(requestId)
91813
+ };
91814
+ }
91815
+ const FILE_REPLY_HINT = `[Saycode · Slack] To send a file back to this thread, add a line \`MEDIA:<path in the working directory>\` to your final answer (up to ${CHANNEL_FILE_REPLY_MAX_FILES} files, ${CHANNEL_FILE_REPLY_MAX_BYTES / 1024 / 1024} MB each).`;
91816
+ function withChannelFileReplyHint(connection, operations) {
91817
+ if (!connection.filesAttachable || !("filesGetUploadURLExternal" in channelProviderContract(connection.provider).operations)) return operations;
91818
+ const prompt = (input) => operations.prompt({ ...input, text: `${input.text}
91819
+
91820
+ ${FILE_REPLY_HINT}` });
91821
+ return Object.assign(Object.create(operations), { prompt });
91822
+ }
91823
+ async function withdrawIntakeDurably(cancelled, connectionKeys, deps) {
91824
+ const undurable = await journalCancelledIntake(cancelled, deps);
91825
+ if (undurable.length === 0) return EMPTY_WITHDRAWAL;
91826
+ const unsafeToReopen = [];
91827
+ for (const key of connectionKeys) {
91828
+ try {
91829
+ await deps.revokeGeneration(key);
91830
+ } catch {
91831
+ unsafeToReopen.push(key);
91832
+ deps.onDiagnostic?.({
91833
+ code: "INTAKE_WITHDRAWAL_NOT_ENFORCEABLE",
91834
+ message: "a withdrawal was neither recorded nor fenced by a generation move"
91835
+ });
91836
+ }
91837
+ }
91838
+ return Object.freeze({ unsafeToReopen: Object.freeze(unsafeToReopen) });
91839
+ }
91840
+ const EMPTY_WITHDRAWAL = Object.freeze({
91841
+ unsafeToReopen: Object.freeze([])
91842
+ });
91843
+ async function journalCancelledIntake(entries, deps) {
91844
+ const undurable = [];
91845
+ for (const entry of entries) {
91846
+ try {
91847
+ await deps.journal.record(entry);
91848
+ } catch {
91849
+ undurable.push(entry.id);
91850
+ deps.onDiagnostic?.({
91851
+ code: "INTAKE_CANCELLATION_NOT_DURABLE",
91852
+ message: "a cancelled request could not be recorded"
91853
+ });
91854
+ }
91855
+ }
91856
+ return Object.freeze(undurable);
91857
+ }
91858
+ function intakeStateForTurnOutcome(outcome) {
91859
+ if (outcome.kind === "final") return "executed";
91860
+ if (outcome.status === "cancelled") return "cancelled";
91861
+ if (outcome.status === "no-final-answer") return "executed";
91862
+ return "failed";
91863
+ }
91864
+ async function settleIntakeEntry(queue, requestId, state, deps) {
91865
+ let settled;
91866
+ try {
91867
+ settled = queue.transition(requestId, state);
91868
+ } catch {
91869
+ return;
91870
+ }
91871
+ try {
91872
+ await deps.journal.record(settled);
91873
+ } catch {
91874
+ deps.onDiagnostic?.({
91875
+ code: "INTAKE_TERMINAL_NOT_DURABLE",
91876
+ message: "a finished request could not be recorded"
91877
+ });
91878
+ }
91879
+ }
90716
91880
  const PROGRESS_MIN_EDIT_INTERVAL_MS = 1500;
90717
91881
  const PROGRESS_HEARTBEAT_MS = 6e4;
90718
91882
  const PROGRESS_TERMINAL_ATTEMPTS = 3;
@@ -91115,64 +92279,6 @@ function frozenRequestBinding(binding) {
91115
92279
  destination: Object.freeze({ ...binding.destination })
91116
92280
  });
91117
92281
  }
91118
- async function sendChannelOutbound(message, deps) {
91119
- const contract = channelProviderContract(deps.provider);
91120
- const mapping = contract.outbound;
91121
- if (message.body.length > mapping.maxTextLength) {
91122
- deps.onDiagnostic?.({
91123
- code: "REPLY_TOO_LONG",
91124
- message: `the reply exceeds ${mapping.maxTextLength} characters for this provider`
91125
- });
91126
- return "failed";
91127
- }
91128
- if (!deps.isCurrent()) return "failed";
91129
- const params = {
91130
- [mapping.conversationParam]: message.destination.conversationRef,
91131
- [mapping.textParam]: message.body
91132
- };
91133
- if (mapping.threadParam && message.destination.threadRef) {
91134
- params[mapping.threadParam] = message.destination.threadRef;
91135
- }
91136
- if (mapping.replyToParam && message.destination.replyToTransportEventId) {
91137
- params[mapping.replyToParam] = mapping.replyToIsStructured && mapping.replyToStructuredField ? { [mapping.replyToStructuredField]: message.destination.replyToTransportEventId } : message.destination.replyToTransportEventId;
91138
- }
91139
- if (deps.interactive && mapping.interactiveParam) {
91140
- params[mapping.interactiveParam] = deps.interactive;
91141
- }
91142
- let result2;
91143
- try {
91144
- result2 = await deps.executor.execute(message.connectionId, {
91145
- operation: mapping.operation,
91146
- params,
91147
- credentialRevision: message.connectionRevision
91148
- }, void 0, () => {
91149
- if (!deps.stillSending()) throw new Error("withdrawn");
91150
- });
91151
- } catch (error) {
91152
- if (error?.code === "SEND_FENCED") {
91153
- deps.onDiagnostic?.({
91154
- code: "REPLY_WITHDRAWN",
91155
- message: "the reply was withdrawn before it was sent"
91156
- });
91157
- return "failed";
91158
- }
91159
- deps.onDiagnostic?.({ code: "REPLY_SEND_UNCONFIRMED", message: "the reply could not be confirmed" });
91160
- return "unknown";
91161
- }
91162
- const refusal = pollRefusal(contract, result2);
91163
- if (refusal.refused) {
91164
- deps.onDiagnostic?.({
91165
- code: "REPLY_REFUSED",
91166
- message: `the provider refused the reply (${refusal.reason})`
91167
- });
91168
- return "failed";
91169
- }
91170
- if (deps.onDelivered) {
91171
- const messageId2 = readProviderMessageId(deps.provider, result2.body);
91172
- if (messageId2) deps.onDelivered(messageId2);
91173
- }
91174
- return "sent";
91175
- }
91176
92282
  function fail(detail) {
91177
92283
  throw new Error(`extension wire message must be JSON-serializable: ${detail}`);
91178
92284
  }
@@ -91612,9 +92718,9 @@ class ChannelReplyRuntime {
91612
92718
  this.bindings.delete(outcome.requestId);
91613
92719
  } else if (launchesContinuation) this.backgroundContinuations.set(outcome.requestId, 1);
91614
92720
  else this.bindings.delete(outcome.requestId);
91615
- const body = outcome.kind === "final" ? outcome.text : channelTurnFailureText(outcome.status);
91616
92721
  const replyBinding = continuationIndex === void 0 ? binding : { ...binding, requestId: `${binding.requestId}:background:${continuationIndex}` };
91617
- await this.enqueueReply(replyBinding, body);
92722
+ const body = outcome.kind === "final" ? this.options.prepareFinal?.(replyBinding, outcome.text) ?? outcome.text : channelTurnFailureText(outcome.status);
92723
+ if (!await this.enqueueReply(replyBinding, body)) this.options.onReplyDiscarded?.(replyBinding.requestId);
91618
92724
  this.drain(binding.sessionId);
91619
92725
  return outcome;
91620
92726
  }
@@ -91707,7 +92813,7 @@ class ChannelReplyRuntime {
91707
92813
  code: "REPLY_NOT_ADMITTED",
91708
92814
  message: `the reply was not admitted (${admission.reason})`
91709
92815
  });
91710
- return;
92816
+ return false;
91711
92817
  }
91712
92818
  try {
91713
92819
  await this.options.journal.recordOutbound(admission.message);
@@ -91717,9 +92823,10 @@ class ChannelReplyRuntime {
91717
92823
  code: "REPLY_NOT_DURABLE",
91718
92824
  message: "the reply could not be recorded"
91719
92825
  });
91720
- return;
92826
+ return false;
91721
92827
  }
91722
92828
  this.options.outbox.commit(admission.message);
92829
+ return true;
91723
92830
  }
91724
92831
  /** Serial per session; a second call while one is draining joins rather than overlapping. */
91725
92832
  drain(sessionId) {
@@ -91750,6 +92857,7 @@ class ChannelReplyRuntime {
91750
92857
  const settled = this.options.outbox.transition(message.id, outcome);
91751
92858
  await this.options.journal.recordOutbound(settled);
91752
92859
  await this.options.journal.recordOutboundTombstone(tombstoneOf(settled));
92860
+ this.options.onReplySettled?.(settled);
91753
92861
  }
91754
92862
  }
91755
92863
  /** Both halves of "may this reply still leave": the live composition *and* a live adapter. */
@@ -91975,11 +93083,22 @@ class ChannelConnectionComposition {
91975
93083
  mintHandle: this.options.mintId
91976
93084
  }),
91977
93085
  progress,
93086
+ filesAttachable: this.options.sessionFiles !== void 0,
91978
93087
  reply: new ChannelReplyRuntime({
91979
93088
  // Display only, and deliberately after every delivery decision: the rail is handed the
91980
93089
  // provider's id for an acknowledgement that has already been delivered, so nothing here
91981
93090
  // can affect whether a reply goes out or how it is recorded.
91982
93091
  onStatusDelivered: (message, messageId2) => progress.adopt(message, messageId2),
93092
+ // After delivery too: an answer's files leave only once the answer itself was sent.
93093
+ ...channelFileReplyHooks(this.options.sessionFiles, {
93094
+ provider: input.provider,
93095
+ principalKey: input.principalKey,
93096
+ executor: this.outboundExecutor,
93097
+ now: this.options.now,
93098
+ isCurrent: (revision) => this.parts.get(input.identity.connectionId) === parts2 && this.options.generations.current(connectionKey) === revision && (this.options.adapterLive?.(input.identity.connectionId, input.provider) ?? false),
93099
+ ...this.options.fetchImpl ? { fetchImpl: this.options.fetchImpl } : {},
93100
+ ...this.options.onDiagnostic ? { onDiagnostic: this.options.onDiagnostic } : {}
93101
+ }),
91983
93102
  ...this.options.formatReply ? { formatReply: this.options.formatReply } : {},
91984
93103
  provider: input.provider,
91985
93104
  connectionId: input.identity.connectionId,
@@ -92146,6 +93265,39 @@ function bindChannelControlRequest(input, deps) {
92146
93265
  return { ok: false, code: typeof code === "string" ? code : "REQUEST_REFUSED" };
92147
93266
  }
92148
93267
  }
93268
+ async function adoptInvitedMentionGrant(input) {
93269
+ const { event, identity, grants } = input;
93270
+ const subject = { connectionId: event.connectionId, senderRef: event.sender.ref, conversationRef: event.conversation.ref };
93271
+ if (grants.find(identity, subject)) return "existing";
93272
+ if (event.invitedMention !== true || event.sender.bot || event.connectionId !== identity.connectionId) return "none";
93273
+ const source2 = grants.list(identity).find((grant) => grant.senderRef === event.sender.ref);
93274
+ if (!source2 || source2.revision !== event.connectionRevision) return "none";
93275
+ try {
93276
+ await grants.putDurable(identity, { ...source2, conversationRef: event.conversation.ref, allowedSessionRefs: [] });
93277
+ return "adopted";
93278
+ } catch {
93279
+ return "none";
93280
+ }
93281
+ }
93282
+ function channelInvitedMentionText() {
93283
+ return channelText({
93284
+ en: "👋 I’ll answer your mentions in this channel from now on. Replies here are visible to everyone in this channel.",
93285
+ ko: "👋 이제 이 채널에서도 멘션에 답할게요. 여기 답변은 채널 멤버 모두에게 보여요."
93286
+ });
93287
+ }
93288
+ async function adoptInvitedMention(input, deps) {
93289
+ const outcome = await adoptInvitedMentionGrant({ event: input.event, identity: deps.parts.identity, grants: deps.grants });
93290
+ if (outcome !== "adopted" || !deps.answerable()) return;
93291
+ await deps.parts.reply.replyStatus({
93292
+ // Its own request id: the acknowledgement that follows owns `status:<requestId>`.
93293
+ requestId: `${input.requestId}:invited`,
93294
+ connectionId: input.connectionId,
93295
+ connectionRevision: input.event.connectionRevision,
93296
+ sessionId: `command:${input.connectionId}`,
93297
+ destination: input.event.destination,
93298
+ boundAt: deps.now()
93299
+ }, channelInvitedMentionText());
93300
+ }
92149
93301
  const SYNCHRONOUS_OPERATIONS = new Set(
92150
93302
  CHANNEL_SESSION_OPERATIONS.filter((operation) => operation !== "prompt")
92151
93303
  );
@@ -92166,6 +93318,7 @@ async function dispatchChannelControl(input, deps) {
92166
93318
  });
92167
93319
  return !!authCustody.current(parts2.principalKey) && !!current2 && current2.revision === input.event.connectionRevision && generations.current(connectionKey) === input.event.connectionRevision;
92168
93320
  };
93321
+ await adoptInvitedMention(input, { parts: parts2, grants, answerable, now: deps.now });
92169
93322
  const word = classifyChannelCommandWord(input.event.text ?? "");
92170
93323
  if (word) {
92171
93324
  if (answerable()) {
@@ -96160,7 +97313,7 @@ function createChannelRequestCoordinator(deps) {
96160
97313
  generations,
96161
97314
  grants,
96162
97315
  custody: authCustody,
96163
- operations,
97316
+ operations: withChannelFileReplyHint(parts2, operations),
96164
97317
  isEstopEngaged: () => options.isEstopEngaged(),
96165
97318
  ...options.mayActForConnection ? { mayActForConnection: options.mayActForConnection } : {},
96166
97319
  ownership,
@@ -98351,6 +99504,11 @@ async function assemble(options, rootDir) {
98351
99504
  now: () => Date.now(),
98352
99505
  mintId: () => randomUUID(),
98353
99506
  onRequestSettled: (requestId) => forgetRequest?.(requestId),
99507
+ // Files a Slack answer names, read from the session's own machine (specs/channel-file-replies).
99508
+ ...options.sessionFileRpc ? { sessionFiles: {
99509
+ locate: createChannelSessionFileLocator({ authFor: (key) => authCustody.current(key), operations }),
99510
+ callMachineRpc: options.sessionFileRpc
99511
+ } } : {},
98354
99512
  approvalOpen: (approvalHandle) => approvalOpen?.(approvalHandle) ?? false,
98355
99513
  onDiagnostic: (detail) => options.onDiagnostic?.(detail),
98356
99514
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},