@buzzni/saycode-cli 0.9.1 → 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.1";
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 = {
@@ -3194,7 +3336,39 @@ const projectScopeKo = {
3194
3336
  "projectScope.grantRevoked": "파일 접근 권한을 철회하고 세션을 다시 시작했습니다.",
3195
3337
  "projectScope.daemonUpgradeRequired": "Aplus CLI 업데이트 후 다시 시도해 주세요."
3196
3338
  };
3339
+ const machineSettingsLabelsKo = {
3340
+ "machineSettings.listRefreshed": "작업PC 목록을 새로 불러왔어요.",
3341
+ "machineSettings.codeGenerated": "등록 코드를 만들었어요.",
3342
+ "machineSettings.nameSaved": "작업PC 이름을 저장했어요.",
3343
+ "machineSettings.nameCleared": "작업PC 이름을 지웠어요.",
3344
+ "machineSettings.nameSaveFailed": "작업PC 이름을 저장하지 못했어요.",
3345
+ "machineSettings.removed": "{name}을(를) 목록에서 삭제했어요.",
3346
+ "machineSettings.zrokStopped": "zrok 공유를 멈췄어요.",
3347
+ "machineSettings.zrokStart": "zrok 시작",
3348
+ "machineSettings.zrokStarting": "zrok 시작 중",
3349
+ "machineSettings.zrokStop": "zrok 멈추기",
3350
+ "machineSettings.externalServerUrl": "외부 서버 주소",
3351
+ "machineSettings.externalServerUrlPlaceholder": "{url}의 zrok 주소",
3352
+ "machineSettings.liveModeOnly": "작업PC 등록과 삭제는 실제 연결에서만 할 수 있어요.",
3353
+ "machineSettings.issuedCodes": "발급한 코드",
3354
+ "machineSettings.generateCode": "등록 코드 만들기",
3355
+ "machineSettings.registrationCode": "등록 코드",
3356
+ "machineSettings.expiresAt": "{time}에 만료",
3357
+ "machineSettings.expired": "만료됨",
3358
+ "machineSettings.copyCommand": "명령 복사",
3359
+ "machineSettings.commandCopied": "복사됨",
3360
+ "machineSettings.loadingCodes": "코드를 불러오는 중…",
3361
+ "machineSettings.loadingMachines": "작업PC를 불러오는 중…",
3362
+ "machineSettings.emptyTitle": "작업PC 없음",
3363
+ "machineSettings.emptyOrganization": "선택한 조직에서 쓸 수 있는 작업PC가 없어요.",
3364
+ "machineSettings.emptyWorkspace": "현재 작업공간에서 쓸 수 있는 작업PC가 없어요.",
3365
+ "machineSettings.rename": "이름 변경",
3366
+ "machineSettings.delete": "삭제",
3367
+ "machineSettings.confirmDelete": "삭제 확인",
3368
+ "machineSettings.organization": "조직"
3369
+ };
3197
3370
  const machineSettingsConsoleKo = {
3371
+ ...machineSettingsLabelsKo,
3198
3372
  "machineConsole.showDetails": "작업PC 상세 정보 보이기",
3199
3373
  "machineConsole.showDetailsHint": "작업PC 선택 목록에서 마우스를 올리거나 키보드로 이동하면 상세 정보를 보여 줍니다.",
3200
3374
  "machineConsole.environmentStatus": "환경·상태",
@@ -3486,9 +3660,15 @@ const sidebarUpdateKo = {
3486
3660
  "sidebarUpdate.completedBody": "Saycode v{version} 업데이트가 완료됐어요.",
3487
3661
  "sidebarUpdate.installing": "업데이트 적용 중…",
3488
3662
  "sidebarUpdate.update": "업데이트",
3489
- "sidebarUpdate.restartNotice": "재시작 전에 대화 상태를 저장합니다.",
3663
+ "sidebarUpdate.restartNotice": "재시작 전에 대화 상태를 저장합니다. 설치가 끝나면 Saycode가 자동으로 다시 열려요.",
3490
3664
  "sidebarUpdate.minimize": "최소화",
3491
- "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": "종료"
3492
3672
  };
3493
3673
  const globalPauseKo = {
3494
3674
  "settings.globalPause.enable": "자동 작업 일시정지",
@@ -4216,8 +4396,8 @@ const designGuideChatKo = {
4216
4396
  "designGuide.chat.card.question": "사용자 질문",
4217
4397
  "designGuide.chat.card.activity": "진행 상태 행",
4218
4398
  "designGuide.chat.card.error": "답변 오류",
4219
- "designGuide.chat.card.preview": "미리보기, 산출물 링크",
4220
- "designGuide.chat.card.artifact": "Chat 산출물",
4399
+ "designGuide.chat.card.preview": "미리보기, 작업물 링크",
4400
+ "designGuide.chat.card.artifact": "Chat 작업물",
4221
4401
  "designGuide.chat.card.checkpoint": "파일 복원지점",
4222
4402
  "designGuide.chat.card.subagent": "보조 AI도구",
4223
4403
  "designGuide.chat.card.queue": "입력 대기열",
@@ -4813,7 +4993,6 @@ const onboardingKo = {
4813
4993
  "onboarding.page.addProject": "첫 프로젝트 추가",
4814
4994
  "onboarding.page.configured": "설정됨",
4815
4995
  "onboarding.page.optional": "선택",
4816
- "onboarding.page.computerReveal": "내부 테스트: 컴퓨터·브라우저 제어 표시",
4817
4996
  "onboarding.page.testNotification": "테스트 알림 보내기",
4818
4997
  "onboarding.page.testNotificationSending": "테스트 알림 보내는 중…",
4819
4998
  "onboarding.page.testNotificationSuccess": "테스트 알림을 보냈습니다.",
@@ -4988,6 +5167,7 @@ const ko = {
4988
5167
  "aiModelPolicy.machineChanged": "선택한 PC에 연결할 수 없어 대화 시작을 멈췄어요. PC를 확인한 뒤 다시 보내 주세요.",
4989
5168
  "aiModelPolicy.policyChanged": "허용 모델 정책이 바뀌어 대화 시작을 멈췄어요. 질문과 첨부를 확인한 뒤 다시 보내 주세요.",
4990
5169
  "aiModelPolicy.noModels": "이 컴퓨터에서 선택할 수 있는 허용 모델이 없어요.",
5170
+ ...personalAiKo,
4991
5171
  ...conversationTabsKo,
4992
5172
  ...questionComposerKo,
4993
5173
  ...aiRecoveryKo,
@@ -5948,7 +6128,7 @@ const ko = {
5948
6128
  "deployModal.pbAdmin": "PocketBase 관리자 계정",
5949
6129
  "deployModal.email": "이메일:",
5950
6130
  "deployModal.credentialsFile": "자격증명 파일:",
5951
- "deployModal.pbPasswordNote": "비밀번호는 배포 산출물의 자격증명 파일에 저장됩니다. 안전한 곳에 보관하세요.",
6131
+ "deployModal.pbPasswordNote": "비밀번호는 배포 작업물의 자격증명 파일에 저장됩니다. 안전한 곳에 보관하세요.",
5952
6132
  // Project settings panel
5953
6133
  "repositorySettings.title": "코드 저장소",
5954
6134
  "repositorySettings.statusSaved": "저장됨",
@@ -6967,6 +7147,7 @@ const ko = {
6967
7147
  "runtimeEvent.claudeNotFound": "Claude Code를 시작하지 못했어요. 이 작업PC에서 실행 파일을 찾지 못했어요. 이 작업PC에 happy-cli를 다시 설치해 주세요.",
6968
7148
  "runtimeEvent.claudeStoppedBySignal": "Claude Code를 시작하지 못했어요. 예기치 않게 멈췄어요({signal}). 이 작업PC의 daemon 로그를 확인해 주세요.",
6969
7149
  "runtimeEvent.claudeCouldNotStart": "이 작업PC에서 Claude Code를 시작하지 못했어요. 자세한 내용은 이 작업PC의 daemon 로그를 확인해 주세요.",
7150
+ "runtimeEvent.claudeNotSignedIn": "이 작업PC에서 Claude Code에 로그인되어 있지 않아요. 이 작업PC의 daemon을 실행하는 사용자로 로그인한 뒤(`claude` 실행 후 /login) 메시지를 다시 보내 주세요.",
6970
7151
  "runtimeEvent.compactionCompleted": "대화 요약을 마쳤습니다. 이제 요약본을 맥락으로 이어갑니다.",
6971
7152
  "runtimeEvent.codexError": "Codex 오류: {detail}",
6972
7153
  "runtimeEvent.codexNoImages": "Codex 로 보낼 수 있는 이미지가 없었습니다.",
@@ -7321,6 +7502,9 @@ const ko = {
7321
7502
  "projectGitView.branchDelete": "Branch 삭제",
7322
7503
  // Remote terminal + API client (non-hook)
7323
7504
  "terminal.connectTimeout": "터미널 연결 시간이 초과되었습니다.",
7505
+ "terminal.strictMachineUnsupported": "보안 강화(strict) 작업PC에는 아직 원격 터미널을 열 수 없습니다. 작업PC에서 직접 터미널을 사용하세요.",
7506
+ "terminal.machineControlUnknown": "이 작업PC의 CLI가 보안 모드를 알려주지 않아 원격 터미널을 열 수 없습니다. 작업PC에서 happy CLI를 최신 버전으로 업데이트하세요.",
7507
+ "terminal.machineKeyUnavailable": "이 작업PC의 키를 열 수 없어 원격 터미널을 열지 않았습니다. 계정 키 잠금을 해제한 뒤 다시 시도하세요.",
7324
7508
  "terminal.connectFailed": "터미널 연결에 실패했습니다.",
7325
7509
  "terminal.cwdPrepFailed": "원격 터미널 작업 디렉토리를 준비하지 못했습니다.",
7326
7510
  "terminal.restoredOutputDivider": "이전 세션 출력",
@@ -7652,8 +7836,8 @@ const ko = {
7652
7836
  "fileDiscovery.results": "검색 결과",
7653
7837
  "fileDiscovery.empty": "일치하는 내용 없음",
7654
7838
  "fileDiscovery.lineChanged": "파일이 변경되어 이동 가능한 가장 가까운 행을 표시합니다.",
7655
- "fileDiscovery.newArtifacts": "새 산출물",
7656
- "fileDiscovery.newArtifact": "새 산출물"
7839
+ "fileDiscovery.newArtifacts": "새 작업물",
7840
+ "fileDiscovery.newArtifact": "새 작업물"
7657
7841
  };
7658
7842
  const CATALOGS = { ko };
7659
7843
  function registerCatalog(language, catalog2) {
@@ -8292,10 +8476,10 @@ In a chat
8292
8476
  /model · /effort · /voice [path] · /detach · /exit live chat commands
8293
8477
 
8294
8478
  Leaving never stops a remote session — it keeps running on the machine.`;
8295
- const record$i = (value2) => !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8479
+ const record$j = (value2) => !!value2 && typeof value2 === "object" && !Array.isArray(value2);
8296
8480
  const ids = (value2) => Array.isArray(value2) && value2.length <= 1e3 && value2.every((id2) => typeof id2 === "string" && id2.trim().length > 0);
8297
8481
  function parseMachineSharing(value2) {
8298
- 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");
8299
8483
  const m = value2.machine;
8300
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");
8301
8485
  return {
@@ -8560,7 +8744,7 @@ function parseCompanyMemberDirectoryPage(value2) {
8560
8744
  nextCursor: rawCursor
8561
8745
  };
8562
8746
  }
8563
- function record$h(value2) {
8747
+ function record$i(value2) {
8564
8748
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid team collaboration response");
8565
8749
  return value2;
8566
8750
  }
@@ -8573,12 +8757,12 @@ function choice(value2, values) {
8573
8757
  return value2;
8574
8758
  }
8575
8759
  function parseCollaboration(value2) {
8576
- const row2 = record$h(value2);
8760
+ const row2 = record$i(value2);
8577
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");
8578
8762
  return {
8579
8763
  teamIds: row2.teamIds.map(identifier$4),
8580
8764
  bindings: row2.bindings.map((value22) => {
8581
- const binding = record$h(value22);
8765
+ const binding = record$i(value22);
8582
8766
  return {
8583
8767
  accountId: identifier$4(binding.accountId),
8584
8768
  membershipOrigin: choice(binding.membershipOrigin, ["unresolved", "team", "preexisting"]),
@@ -8590,8 +8774,8 @@ function parseCollaboration(value2) {
8590
8774
  };
8591
8775
  }
8592
8776
  function parseMutation(value2) {
8593
- const row2 = record$h(value2);
8594
- 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);
8595
8779
  if (sync && [sync.synced, sync.failed, sync.rejected].some((value22) => !Number.isSafeInteger(value22) || Number(value22) < 0)) throw new Error("Invalid team synchronization counts");
8596
8780
  return { collaboration: parseCollaboration(row2.collaboration), sync, ...parseProjectMemberRemovalResult(row2) };
8597
8781
  }
@@ -8599,7 +8783,7 @@ function createProjectTeamCollaborationApi(request2) {
8599
8783
  const path2 = (id2) => `/api/projects/${encodeURIComponent(id2)}/collaboration/teams`;
8600
8784
  return {
8601
8785
  async getProjectTeamCollaboration(id2) {
8602
- return parseCollaboration(record$h(await request2(path2(id2))).collaboration);
8786
+ return parseCollaboration(record$i(await request2(path2(id2))).collaboration);
8603
8787
  },
8604
8788
  async updateProjectTeamCollaboration(id2, teamIds) {
8605
8789
  if (teamIds.length > 1e3) throw new Error("Too many collaboration teams");
@@ -8613,6 +8797,8 @@ function createProjectTeamCollaborationApi(request2) {
8613
8797
  }
8614
8798
  const AI_GROUP_ERROR_KINDS = [
8615
8799
  "AI_GROUP_ACCOUNT_IDENTITY_UNAVAILABLE",
8800
+ "AI_GROUP_ACCOUNT_KEY_LOCKED",
8801
+ "AI_GROUP_ACCOUNT_KEY_REQUIRED",
8616
8802
  "AI_GROUP_ACCOUNT_MISSING",
8617
8803
  "AI_GROUP_APPLY_BUSY",
8618
8804
  "AI_GROUP_APPLY_FAILED",
@@ -8638,6 +8824,8 @@ const AI_GROUP_ERROR_KINDS = [
8638
8824
  "AI_GROUP_ORGANIZATION_KEY_REQUIRED",
8639
8825
  "AI_GROUP_PAYLOAD_CONFLICT",
8640
8826
  "AI_GROUP_PAYLOAD_TOO_LARGE",
8827
+ "AI_GROUP_PC_NOT_ASSIGNED",
8828
+ "AI_GROUP_PC_REREGISTER_REQUIRED",
8641
8829
  "AI_GROUP_PREPARE_FAILED",
8642
8830
  "AI_GROUP_RECEIPT_INVALID",
8643
8831
  "AI_GROUP_RECEIPT_MISMATCH",
@@ -8668,11 +8856,11 @@ function parseAiGroupErrorKind(value2) {
8668
8856
  return typeof value2 === "string" && AI_GROUP_ERROR_KINDS.includes(value2) ? value2 : "UNKNOWN_ERROR";
8669
8857
  }
8670
8858
  const MANAGED_ACCOUNT_ID = /^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/i;
8671
- function record$g(value2) {
8859
+ function record$h(value2) {
8672
8860
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("Invalid AI group metadata");
8673
8861
  return value2;
8674
8862
  }
8675
- function text$8(value2, max = 254) {
8863
+ function text$9(value2, max = 254) {
8676
8864
  if (typeof value2 !== "string" || !value2 || value2.length > max) throw new Error("Invalid AI group metadata");
8677
8865
  return value2;
8678
8866
  }
@@ -8697,31 +8885,31 @@ function receiptMetadata(result2) {
8697
8885
  ...result2.assignmentGeneration == null ? {} : { assignmentGeneration: positiveInteger(result2.assignmentGeneration) },
8698
8886
  ...result2.fingerprint == null ? {} : { fingerprint: digest$2(result2.fingerprint) },
8699
8887
  ...result2.appliedCredentials == null ? {} : { appliedCredentials: list$1(result2.appliedCredentials, 500).map((entry) => {
8700
- const credential = record$g(entry), managedAccountId = text$8(credential.managedAccountId, 80);
8888
+ const credential = record$h(entry), managedAccountId = text$9(credential.managedAccountId, 80);
8701
8889
  if (!MANAGED_ACCOUNT_ID.test(managedAccountId)) throw new Error("Invalid AI group metadata");
8702
8890
  return { managedAccountId, credentialGeneration: positiveInteger(credential.credentialGeneration) };
8703
8891
  }) }
8704
8892
  };
8705
8893
  }
8706
8894
  function parseAiGroupAssignments(value2, companyId) {
8707
- const body = record$g(value2);
8895
+ const body = record$h(value2);
8708
8896
  if (body.version !== 1 || body.companyId !== companyId) throw new Error("Invalid AI group scope");
8709
8897
  return { version: 1, companyId, groups: list$1(body.groups, 1e3).map((value22) => {
8710
- const group = record$g(value22);
8898
+ const group = record$h(value22);
8711
8899
  return {
8712
- credentialGroupId: text$8(group.credentialGroupId, 128),
8713
- credentialGroupName: text$8(group.credentialGroupName, 80),
8714
- 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),
8715
8903
  accounts: list$1(group.accounts, 500).map((value3) => {
8716
- const account = record$g(value3), identity = text$8(account.identity);
8904
+ const account = record$h(value3), identity = text$9(account.identity);
8717
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");
8718
8906
  const managed = account.credentialType === "setup_token";
8719
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");
8720
8908
  return {
8721
- bundleId: text$8(account.bundleId, 128),
8909
+ bundleId: text$9(account.bundleId, 128),
8722
8910
  identity,
8723
8911
  provider: provider$1(account.provider),
8724
- label: text$8(account.label),
8912
+ label: text$9(account.label),
8725
8913
  kind: account.kind,
8726
8914
  bundleVersion: Number(account.bundleVersion),
8727
8915
  ...managed ? { credentialType: "setup_token", managedAccountId: String(account.managedAccountId), credentialGeneration: Number(account.credentialGeneration), authState: "unverified" } : {}
@@ -8729,11 +8917,11 @@ function parseAiGroupAssignments(value2, companyId) {
8729
8917
  })
8730
8918
  };
8731
8919
  }), results: list$1(body.results, 5e3).map((value22) => {
8732
- const result2 = record$g(value22), status = result2.status;
8920
+ const result2 = record$h(value22), status = result2.status;
8733
8921
  if (status !== "pending" && status !== "applied" && status !== "revoked" && status !== "failed" && status !== "action_required") throw new Error("Invalid AI group result");
8734
8922
  const errorKind = parseAiGroupErrorKind(result2.errorKind);
8735
8923
  return {
8736
- machineId: text$8(result2.machineId, 128),
8924
+ machineId: text$9(result2.machineId, 128),
8737
8925
  provider: provider$1(result2.provider),
8738
8926
  status,
8739
8927
  ...errorKind ? { errorKind } : {},
@@ -8804,8 +8992,10 @@ function parseCollectorAssignment(value2) {
8804
8992
  const machineId = v.machineId === null ? null : identifier$3(v.machineId);
8805
8993
  const credentialGeneration = int$1(v.credentialGeneration, 0), policyRevision = int$1(v.policyRevision, 0);
8806
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");
8807
8996
  return {
8808
8997
  version: 1,
8998
+ principalType: v.principalType ?? "user",
8809
8999
  enabled: v.enabled,
8810
9000
  probeAllowed: v.probeAllowed,
8811
9001
  status: v.status,
@@ -8857,26 +9047,26 @@ function createClaudeCollectorApi(transport) {
8857
9047
  }
8858
9048
  };
8859
9049
  }
8860
- const record$f = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
8861
- 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;
8862
9052
  function parseWindow(value2) {
8863
- const row2 = record$f(value2);
8864
- 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;
8865
9055
  return { id: row2.id, openedAt: row2.openedAt, endsAt: row2.endsAt, waitMinutes: row2.waitMinutes };
8866
9056
  }
8867
9057
  function parseMe(value2) {
8868
- const row2 = record$f(value2);
9058
+ const row2 = record$g(value2);
8869
9059
  if (!row2) return null;
8870
9060
  const { hasLatestGrant, pending: pending2, autoEligible, autoAt, held } = row2;
8871
9061
  if (typeof hasLatestGrant !== "boolean" || typeof pending2 !== "boolean" || typeof autoEligible !== "boolean" || typeof held !== "boolean") return null;
8872
- if (autoAt !== null && !timestamp(autoAt)) return null;
9062
+ if (autoAt !== null && !timestamp$1(autoAt)) return null;
8873
9063
  return { hasLatestGrant, pending: pending2, autoEligible, autoAt, held };
8874
9064
  }
8875
9065
  function companyKeyAutoWindowHeaders(options) {
8876
9066
  return options?.distributor ? { "X-Saycode-Auto-Grant-Client": "desktop" } : {};
8877
9067
  }
8878
9068
  function parseCompanyKeyAutoWindow(raw) {
8879
- const body = record$f(raw);
9069
+ const body = record$g(raw);
8880
9070
  return { window: parseWindow(body?.window), me: parseMe(body?.me) };
8881
9071
  }
8882
9072
  function companyKeyGrantFailure(error) {
@@ -8886,7 +9076,7 @@ function companyKeyGrantFailure(error) {
8886
9076
  const body = "body" in error ? error.body : void 0;
8887
9077
  if (typeof body === "string") {
8888
9078
  try {
8889
- const parsed2 = record$f(JSON.parse(body));
9079
+ const parsed2 = record$g(JSON.parse(body));
8890
9080
  if (typeof parsed2?.error === "string") reason = parsed2.error;
8891
9081
  } catch {
8892
9082
  }
@@ -11557,32 +11747,32 @@ function invalidateRendererQueries(kind, scope2) {
11557
11747
  }
11558
11748
  const ROUTING_POLICY = "org-shared-difficulty-routing.v1";
11559
11749
  const ROUTING_AGENTS = ["claude", "codex"];
11560
- function record$e(value2) {
11750
+ function record$f(value2) {
11561
11751
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
11562
11752
  }
11563
11753
  function id$2(value2) {
11564
11754
  return typeof value2 === "string" && value2.length > 0 && value2.length <= 256;
11565
11755
  }
11566
11756
  function parseRoutingResult(value2) {
11567
- const r = record$e(value2);
11568
- const baseRoute = record$e(r?.baseRoute);
11569
- 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);
11570
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;
11571
11761
  return r;
11572
11762
  }
11573
11763
  function parseRoutingPolicy(value2) {
11574
- const p = record$e(value2);
11764
+ const p = record$f(value2);
11575
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;
11576
11766
  if (p.enabled && !id$2(p.hostMachineId)) return null;
11577
11767
  return p;
11578
11768
  }
11579
11769
  function parseRoutingSettings(value2) {
11580
- 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);
11581
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");
11582
11772
  return v;
11583
11773
  }
11584
11774
  function canNegotiateOrgRouting(input) {
11585
- const capability = record$e(input.machine?.difficultyRouting);
11775
+ const capability = record$f(input.machine?.difficultyRouting);
11586
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;
11587
11777
  }
11588
11778
  function prepareRoutingIntent(input) {
@@ -15045,7 +15235,7 @@ const ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
15045
15235
  inst.keyType = def.keyType;
15046
15236
  inst.valueType = def.valueType;
15047
15237
  });
15048
- function record$d(keyType, valueType, params) {
15238
+ function record$e(keyType, valueType, params) {
15049
15239
  return new ZodRecord({
15050
15240
  type: "record",
15051
15241
  keyType,
@@ -15279,7 +15469,7 @@ function superRefine(fn) {
15279
15469
  }
15280
15470
  function json(params) {
15281
15471
  const jsonSchema = lazy(() => {
15282
- 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)]);
15283
15473
  });
15284
15474
  return jsonSchema;
15285
15475
  }
@@ -17577,14 +17767,14 @@ const scriptAutomationPayloadSchema = strictObject({
17577
17767
  entrypoint: entrypointSchema,
17578
17768
  args: array$4(string$1().max(4e3)).max(64),
17579
17769
  timeoutSeconds: number().int().min(1).max(1800),
17580
- secretRefs: record$d(secretName, string$1().min(1).max(200)),
17770
+ secretRefs: record$e(secretName, string$1().min(1).max(200)),
17581
17771
  allowedOrigins: array$4(originSchema).max(100)
17582
17772
  }),
17583
17773
  schedule: scriptScheduleSchema.nullable(),
17584
17774
  externalEnabled: boolean(),
17585
- inputSchema: record$d(string$1(), json())
17775
+ inputSchema: record$e(string$1(), json())
17586
17776
  });
17587
- strictObject({ input: record$d(string$1(), json()) });
17777
+ strictObject({ input: record$e(string$1(), json()) });
17588
17778
  const scriptAdmissionSchema = scriptAutomationPayloadSchema.pick({
17589
17779
  schedule: true,
17590
17780
  externalEnabled: true,
@@ -17611,7 +17801,7 @@ discriminatedUnion("operation", [
17611
17801
  strictObject({ operation: literal("upsert"), registration: scriptRegistrationRequestSchema }),
17612
17802
  strictObject({ operation: literal("runs"), automationId: managementId, runId: managementId.optional() }),
17613
17803
  strictObject({ operation: literal("artifact"), artifactId: managementId }),
17614
- 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 }),
17615
17805
  strictObject({ operation: literal("cancel"), automationId: managementId, runId: managementId })
17616
17806
  ]);
17617
17807
  const scriptAutomationRowSchema = object$3({
@@ -17902,14 +18092,14 @@ function parseCloudLoginResult(raw) {
17902
18092
  }
17903
18093
  const HANDOFF_ID_PATTERN = /^mph-[A-Za-z0-9_-]{22}$/;
17904
18094
  const CAPABILITY_PATTERN = /^[A-Za-z0-9_-]{43}$/;
17905
- function record$c(value2) {
18095
+ function record$d(value2) {
17906
18096
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
17907
18097
  }
17908
18098
  function validExpiry$1(value2) {
17909
18099
  return typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0;
17910
18100
  }
17911
18101
  function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
17912
- const value2 = record$c(raw);
18102
+ const value2 = record$d(raw);
17913
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)) {
17914
18104
  throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
17915
18105
  }
@@ -17933,7 +18123,7 @@ function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
17933
18123
  };
17934
18124
  }
17935
18125
  function parsePendingMfaPasskeyExchange(raw) {
17936
- const value2 = record$c(raw);
18126
+ const value2 = record$d(raw);
17937
18127
  if (!value2 || value2.status !== "pending" || !validExpiry$1(value2.expiresAt)) {
17938
18128
  throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
17939
18129
  }
@@ -18172,6 +18362,9 @@ async function preDecryptSessionHeaders(sessions, accountPrivateKey, sinceGenera
18172
18362
  }
18173
18363
  }));
18174
18364
  }
18365
+ function readsAsStrictMachineControl(machine) {
18366
+ return !machine.serverDataEncryptionKey && typeof machine.serverRpcKeyEnvelope === "string" && machine.serverRpcKeyEnvelope !== "";
18367
+ }
18175
18368
  const MACHINE_KEY_ATTESTATION_VERSION = 1;
18176
18369
  const DIGEST_DOMAIN = new TextEncoder().encode("happy machine key fingerprint v1");
18177
18370
  const HEADER_BYTES = 1 + nacl.box.publicKeyLength + nacl.box.nonceLength;
@@ -18485,7 +18678,7 @@ function personalMachinesPath(companyId) {
18485
18678
  function myMachinesPath(companyId) {
18486
18679
  return `/api/admin/companies/${encodeURIComponent(companyId)}/my-machines`;
18487
18680
  }
18488
- function record$b(value2) {
18681
+ function record$c(value2) {
18489
18682
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
18490
18683
  }
18491
18684
  function array$3(value2) {
@@ -18504,7 +18697,7 @@ function provider(value2) {
18504
18697
  return PROVIDERS.includes(str$1(value2)) ? str$1(value2) : null;
18505
18698
  }
18506
18699
  function parseBundle(value2) {
18507
- const row2 = record$b(value2);
18700
+ const row2 = record$c(value2);
18508
18701
  const id2 = str$1(row2.id);
18509
18702
  const bundleProvider = provider(row2.provider);
18510
18703
  if (!id2 || !bundleProvider) return null;
@@ -18523,7 +18716,7 @@ function parseBundle(value2) {
18523
18716
  };
18524
18717
  }
18525
18718
  function parseDeployment(value2) {
18526
- const row2 = record$b(value2);
18719
+ const row2 = record$c(value2);
18527
18720
  const machineId = str$1(row2.machineId);
18528
18721
  const bundleId = str$1(row2.bundleId);
18529
18722
  if (!machineId || !bundleId) return null;
@@ -18543,10 +18736,10 @@ function parseDeployment(value2) {
18543
18736
  };
18544
18737
  }
18545
18738
  function parseStatus(value2) {
18546
- const row2 = record$b(value2);
18739
+ const row2 = record$c(value2);
18547
18740
  const statusProvider = provider(row2.provider);
18548
18741
  if (!statusProvider) return void 0;
18549
- const rotationRow = record$b(row2.rotation);
18742
+ const rotationRow = record$c(row2.rotation);
18550
18743
  const state = str$1(rotationRow.state);
18551
18744
  const warningKinds = Array.isArray(rotationRow.warningKinds) ? [...new Set(rotationRow.warningKinds.filter(
18552
18745
  (kind) => kind === "ACCOUNT_NEEDS_REAUTH" || kind === "NO_COMPARISON"
@@ -18571,7 +18764,7 @@ function parseStatus(value2) {
18571
18764
  };
18572
18765
  }
18573
18766
  function parseMachineResult(value2) {
18574
- const row2 = record$b(value2);
18767
+ const row2 = record$c(value2);
18575
18768
  const machineId = str$1(row2.machineId);
18576
18769
  if (!machineId) return null;
18577
18770
  if (row2.ok !== true) {
@@ -18586,28 +18779,28 @@ function parseMachineResult(value2) {
18586
18779
  };
18587
18780
  }
18588
18781
  function parseAiCredentialVaultSnapshot(value2) {
18589
- const body = record$b(value2);
18782
+ const body = record$c(value2);
18590
18783
  return {
18591
18784
  bundles: array$3(body.bundles).map(parseBundle).filter((bundle) => bundle !== null),
18592
18785
  deployments: array$3(body.deployments).map(parseDeployment).filter((deployment) => deployment !== null)
18593
18786
  };
18594
18787
  }
18595
18788
  function parseAiCredentialCaptureResponse(value2) {
18596
- const body = record$b(value2);
18789
+ const body = record$c(value2);
18597
18790
  const bundle = parseBundle(body.bundle);
18598
18791
  if (!bundle) throw new Error("AI credential capture response did not contain a bundle");
18599
18792
  return { bundle, warnings: array$3(body.warnings).filter((warning) => typeof warning === "string") };
18600
18793
  }
18601
18794
  function parseAiCredentialBatchResult(value2) {
18602
- const body = record$b(value2);
18795
+ const body = record$c(value2);
18603
18796
  return {
18604
18797
  results: array$3(body.results).map(parseMachineResult).filter((result2) => result2 !== null),
18605
18798
  warnings: array$3(body.warnings).filter((warning) => typeof warning === "string")
18606
18799
  };
18607
18800
  }
18608
18801
  function parseSelfServiceMachines(value2) {
18609
- return array$3(record$b(value2).machines).map((entry) => {
18610
- const row2 = record$b(entry);
18802
+ return array$3(record$c(value2).machines).map((entry) => {
18803
+ const row2 = record$c(entry);
18611
18804
  return {
18612
18805
  machineId: str$1(row2.machineId),
18613
18806
  type: row2.type === "shared" ? "shared" : "personal",
@@ -18620,8 +18813,8 @@ function parseSelfServiceMachines(value2) {
18620
18813
  }).filter((machine) => machine.machineId !== "");
18621
18814
  }
18622
18815
  function parsePersonalMachines(value2) {
18623
- return array$3(record$b(value2).machines).map((entry) => {
18624
- const row2 = record$b(entry);
18816
+ return array$3(record$c(value2).machines).map((entry) => {
18817
+ const row2 = record$c(entry);
18625
18818
  const type = row2.type === "shared" ? "shared" : "personal";
18626
18819
  return {
18627
18820
  machineId: str$1(row2.machineId),
@@ -18656,7 +18849,7 @@ const orgSearchPaths = {
18656
18849
  cursors: (companyId, projectId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/cursors${projectId === void 0 ? "" : `?projectId=${encodeURIComponent(projectId)}`}`,
18657
18850
  progress: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/progress`
18658
18851
  };
18659
- function record$a(value2) {
18852
+ function record$b(value2) {
18660
18853
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
18661
18854
  }
18662
18855
  function str(value2, fallback = "") {
@@ -18667,9 +18860,9 @@ function num(value2, fallback = 0) {
18667
18860
  }
18668
18861
  const PHASES = ["idle", "waiting", "indexing", "done"];
18669
18862
  function parseOrgSearchCursors(raw) {
18670
- 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 : [];
18671
18864
  return list2.flatMap((item) => {
18672
- const c = record$a(item);
18865
+ const c = record$b(item);
18673
18866
  if (typeof c.sessionId !== "string" || typeof c.lastSeq !== "number") return [];
18674
18867
  return [{
18675
18868
  sessionId: c.sessionId,
@@ -18681,7 +18874,7 @@ function parseOrgSearchCursors(raw) {
18681
18874
  });
18682
18875
  }
18683
18876
  function parseProjectProgress(raw) {
18684
- const p = record$a(raw);
18877
+ const p = record$b(raw);
18685
18878
  return {
18686
18879
  projectId: str(p.projectId),
18687
18880
  sessionsTotal: num(p.sessionsTotal),
@@ -18694,7 +18887,7 @@ function parseProjectProgress(raw) {
18694
18887
  };
18695
18888
  }
18696
18889
  function parseOrgSearchProgress(raw) {
18697
- const r = record$a(raw);
18890
+ const r = record$b(raw);
18698
18891
  return {
18699
18892
  phase: PHASES.includes(r.phase) ? r.phase : "idle",
18700
18893
  sessionsTotal: num(r.sessionsTotal),
@@ -19171,7 +19364,7 @@ const PUBLICATION_KEYS = /* @__PURE__ */ new Set([
19171
19364
  function fail$9(detail) {
19172
19365
  throw new Error(`artifact publication response is invalid: ${detail}`);
19173
19366
  }
19174
- function record$9(value2, label) {
19367
+ function record$a(value2, label) {
19175
19368
  if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) fail$9(`${label} must be an object`);
19176
19369
  return value2;
19177
19370
  }
@@ -19217,7 +19410,7 @@ function sourceIdentity(source2) {
19217
19410
  ]) : JSON.stringify([source2.chatId, source2.path]);
19218
19411
  }
19219
19412
  function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
19220
- const publication = record$9(value2, "publication");
19413
+ const publication = record$a(value2, "publication");
19221
19414
  if (Object.keys(publication).some((key) => !PUBLICATION_KEYS.has(key))) {
19222
19415
  fail$9("publication contains an unexpected field");
19223
19416
  }
@@ -19250,7 +19443,7 @@ function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
19250
19443
  };
19251
19444
  }
19252
19445
  function parseStaticArtifactPublicationResult(value2, options) {
19253
- const response2 = record$9(value2, "response");
19446
+ const response2 = record$a(value2, "response");
19254
19447
  if (options.operation === "publish") {
19255
19448
  exactKeys$3(response2, ["publication", "publicUrl", "reused"]);
19256
19449
  if (typeof response2.reused !== "boolean") fail$9("reused is invalid");
@@ -19272,20 +19465,20 @@ const MAX_ID = 256;
19272
19465
  function isRecord$4(value2) {
19273
19466
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
19274
19467
  }
19275
- function text$7(value2, max = MAX_TEXT) {
19468
+ function text$8(value2, max = MAX_TEXT) {
19276
19469
  return typeof value2 === "string" && value2.length >= 1 && value2.length <= max;
19277
19470
  }
19278
19471
  function list(value2, minItems) {
19279
- 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));
19280
19473
  }
19281
19474
  function parseLessonCandidate(value2) {
19282
19475
  if (!isRecord$4(value2) || value2.t !== "lesson-candidate") return null;
19283
19476
  if (Object.keys(value2).some((key) => !EVENT_KEYS.has(key))) return null;
19284
19477
  const { candidateId, revision, payloadHash, lesson } = value2;
19285
- 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;
19286
19479
  if (typeof revision !== "number" || !Number.isInteger(revision) || revision < 0) return null;
19287
19480
  if (!isRecord$4(lesson) || Object.keys(lesson).some((key) => !LESSON_KEYS.has(key))) return null;
19288
- 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;
19289
19482
  if (!list(lesson.steps, 1) || !list(lesson.validation, 1) || !list(lesson.failureModes, 0)) return null;
19290
19483
  if (lesson.validVersions !== void 0 && !list(lesson.validVersions, 0)) return null;
19291
19484
  return {
@@ -19511,22 +19704,27 @@ const CLAUDE_PROVIDER_PATTERN = /rate.?limit|too many requests|overloaded|model[
19511
19704
  const INACTIVITY_PREFIX = "stopped automatically";
19512
19705
  const PROCESS_EXITED_NOTICE = "process exited unexpectedly";
19513
19706
  const CLAUDE_OAUTH_FAILURE = /^Failed to authenticate\. API Error: 401 OAuth access token is invalid\.$/i;
19707
+ const CLAUDE_NOT_SIGNED_IN = /^Not logged in · Please run \/login/i;
19514
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;
19515
19709
  function unsupportedModelSurface(text2) {
19516
19710
  if (detectModelLimitNotice(text2) || text2.toLowerCase().includes("requires a newer version of codex")) return null;
19517
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;
19518
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
+ }
19519
19717
  function messageErrorSurface(message) {
19520
19718
  if ((message.role === "agent" || message.role === "system") && !message.thinking && !message.toolCall && message.errorSurface?.layer === "provider" && ["codex_error", "claude_error"].includes(message.errorSurface.code)) {
19521
19719
  const text22 = message.content ?? "";
19522
- 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;
19523
19721
  if (refined) return refined;
19524
19722
  }
19525
19723
  if (message.errorSurface && isErrorSurfaceLayer(message.errorSurface.layer)) return message.errorSurface;
19526
19724
  if (message.role !== "agent" || message.thinking || message.toolCall) return null;
19527
19725
  const text2 = message.content?.trim() ?? "";
19528
19726
  if (CLAUDE_SUBSCRIPTION_REFUSAL.test(text2)) return { layer: "auth", code: "subscription_access_disabled", retryable: false };
19529
- if (CLAUDE_OAUTH_FAILURE.test(text2)) return { layer: "auth", code: "auth_failed", retryable: false };
19727
+ if (CLAUDE_OAUTH_FAILURE.test(text2) || CLAUDE_NOT_SIGNED_IN.test(text2)) return { layer: "auth", code: "auth_failed", retryable: false };
19530
19728
  return /^(you['’]ve|you have) reached your [a-z0-9.-]+ limit\./i.test(text2) && detectModelLimitNotice(text2) ? { layer: "provider", code: "model_limit", retryable: true } : null;
19531
19729
  }
19532
19730
  function classifyErrorSurface(rawText) {
@@ -19536,7 +19734,7 @@ function classifyErrorSurface(rawText) {
19536
19734
  if (text2 === "") return null;
19537
19735
  const lower = text2.toLowerCase();
19538
19736
  if (CLAUDE_SUBSCRIPTION_REFUSAL.test(text2)) return { layer: "auth", code: "subscription_access_disabled", retryable: false };
19539
- if (CLAUDE_OAUTH_FAILURE.test(text2)) return { layer: "auth", code: "auth_failed", retryable: false };
19737
+ if (CLAUDE_OAUTH_FAILURE.test(text2) || CLAUDE_NOT_SIGNED_IN.test(text2)) return { layer: "auth", code: "auth_failed", retryable: false };
19540
19738
  if (detectModelLimitNotice(text2)) {
19541
19739
  return { layer: "provider", code: "model_limit", retryable: true };
19542
19740
  }
@@ -19553,7 +19751,7 @@ function classifyErrorSurface(rawText) {
19553
19751
  if (lower.startsWith(CODEX_ERROR_PREFIX) && lower.includes("requires a newer version of codex")) {
19554
19752
  return { layer: "provider", code: "codex_needs_update", retryable: false };
19555
19753
  }
19556
- const modelFailure = unsupportedModelSurface(text2);
19754
+ const modelFailure = unsupportedModelSurface(text2) ?? (lower.startsWith(CODEX_ERROR_PREFIX) ? capacitySurface(text2) : null);
19557
19755
  if (modelFailure) return modelFailure;
19558
19756
  if (lower.startsWith(CLAUDE_ERROR_PREFIX) && !CLAUDE_PROVIDER_PATTERN.test(lower)) return null;
19559
19757
  return { layer: "provider", code: lower.startsWith(CODEX_ERROR_PREFIX) ? "codex_error" : "claude_error", retryable: true };
@@ -19573,6 +19771,8 @@ const KNOWN_NOTICES = [
19573
19771
  ["Claude Code could not start: it refuses to run with permission prompts skipped as root. Run this machine's daemon as a regular user.", "runtimeEvent.claudeRootRefused"],
19574
19772
  ["Claude Code could not start: its executable could not be found on this machine. Reinstall happy-cli on it.", "runtimeEvent.claudeNotFound"],
19575
19773
  ["Claude Code could not start on this machine. Check this machine's daemon log for details.", "runtimeEvent.claudeCouldNotStart"],
19774
+ // happy-cli `CLAUDE_NOT_SIGNED_IN_NOTICE` (R36).
19775
+ ["Claude Code is not signed in on this machine. Sign in as the user that runs this machine's daemon (run `claude`, then /login) and send the message again.", "runtimeEvent.claudeNotSignedIn"],
19576
19776
  ["No supported images were available to send to Codex.", "runtimeEvent.codexNoImages"],
19577
19777
  [
19578
19778
  "Force-stopped active task after interrupt timeout. Codex backend was restarted and the previous thread was resumed.",
@@ -28728,6 +28928,53 @@ async function prepareMcpCallerGrantEnvelope(input, deps) {
28728
28928
  return { ok: false, reason: "invalid-key" };
28729
28929
  }
28730
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
+ }
28731
28978
  const CLEARABLE_STRING_KEYS = /* @__PURE__ */ new Set(["branch", "baseBranch"]);
28732
28979
  function buildWorktreeMutationBody(input, options = {}) {
28733
28980
  return JSON.stringify(Object.fromEntries(
@@ -29499,6 +29746,7 @@ function normalizeMachine(row2, candidateKeys, dataKeyFields = null) {
29499
29746
  ...authenticatedEnvelopes ? { authenticatedEnvelopes } : {},
29500
29747
  // specs/e2ee-machine-control-boundary R22 — the attestation decision for the key this app opened.
29501
29748
  ...keyTrust ? { keyTrust } : {},
29749
+ ...readsAsStrictMachineControl(row2) ? { machineControl: "strict" } : {},
29502
29750
  channelSupport,
29503
29751
  // Owner metadata only: the host key in it is what Desktop seals credentials to.
29504
29752
  channelHost: parseChannelHostCapability(decrypted.channelHost),
@@ -29905,6 +30153,30 @@ class ApiClient {
29905
30153
  const data = await this.request("/api/companies/me", { boundary: "api" });
29906
30154
  return Array.isArray(data) ? data : data.companies ?? [];
29907
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
+ }
29908
30180
  async getAiGroupAssignments(companyId, signal, mfaSessionToken) {
29909
30181
  const value2 = await this.request(`/api/companies/${encodeURIComponent(companyId)}/ai-groups/me`, {
29910
30182
  boundary: "api",
@@ -33107,6 +33379,10 @@ function renderTurnEnd(status, reason) {
33107
33379
  const TOOL_SUMMARY_MAX_LENGTH = 80;
33108
33380
  const TOOL_PROGRESS_VISIBLE_LINES = 3;
33109
33381
  const TOOL_SUMMARY_KEYS = ["command", "file_path", "path", "pattern", "query", "url", "prompt"];
33382
+ const TITLE_TOOL = "mcp__happy__change_title";
33383
+ function isInternalTitleCall(name, args) {
33384
+ return name === TITLE_TOOL || name === "ToolSearch" && args.query === `select:${TITLE_TOOL}`;
33385
+ }
33110
33386
  function truncate(value2, max) {
33111
33387
  if (value2.length <= max) return value2;
33112
33388
  return `${value2.slice(0, max - 1)}…`;
@@ -33136,6 +33412,10 @@ function renderSessionEvent(ev, envelopeRole, progressByCall, options) {
33136
33412
  case "service":
33137
33413
  return textLines(ev.text, "status");
33138
33414
  case "tool-call-start": {
33415
+ if (isInternalTitleCall(ev.name, ev.args)) {
33416
+ progressByCall.set(ev.call, { shown: 0, hidden: 0, lastHidden: null, muted: true });
33417
+ return [];
33418
+ }
33139
33419
  progressByCall.set(ev.call, { shown: 0, hidden: 0, lastHidden: null });
33140
33420
  const summary = summarizeToolArgs(ev.args) ?? (ev.description?.trim() || null);
33141
33421
  return [{ style: "tool", text: summary ? `⏺ ${ev.name}(${summary})` : `⏺ ${ev.name}` }];
@@ -33143,6 +33423,7 @@ function renderSessionEvent(ev, envelopeRole, progressByCall, options) {
33143
33423
  case "tool-call-progress": {
33144
33424
  const state = progressByCall.get(ev.call) ?? { shown: 0, hidden: 0, lastHidden: null };
33145
33425
  progressByCall.set(ev.call, state);
33426
+ if (state.muted) return [];
33146
33427
  const lines = [];
33147
33428
  for (const line2 of ev.lines) {
33148
33429
  if (ev.stream === "stderr") lines.push({ style: "error", text: ` │ ${line2}` });
@@ -34420,7 +34701,7 @@ class ChatInputController {
34420
34701
  this.readline.off("close", this.handleClose);
34421
34702
  }
34422
34703
  }
34423
- function readString$3(source2, key) {
34704
+ function readString$4(source2, key) {
34424
34705
  const value2 = source2[key];
34425
34706
  return typeof value2 === "string" && value2 ? value2 : void 0;
34426
34707
  }
@@ -34439,8 +34720,8 @@ function normalizeSessionRows(rows, candidateKeys) {
34439
34720
  if (!metadata) continue;
34440
34721
  sessions.push({
34441
34722
  id: row2.id,
34442
- machineId: readString$3(metadata, "machineId") ?? "",
34443
- directory: readString$3(metadata, "path") ?? "",
34723
+ machineId: readString$4(metadata, "machineId") ?? "",
34724
+ directory: readString$4(metadata, "path") ?? "",
34444
34725
  summary: readSummary(metadata),
34445
34726
  active: row2.active === true,
34446
34727
  updatedAt: row2.updatedAt ?? 0,
@@ -37945,7 +38226,7 @@ async function replyToPermission(connection, request2, answer) {
37945
38226
  ...answer === "approve-always" ? { mode: "bypassPermissions" } : {}
37946
38227
  });
37947
38228
  }
37948
- function readString$2(source2, key) {
38229
+ function readString$3(source2, key) {
37949
38230
  const value2 = source2?.[key];
37950
38231
  return typeof value2 === "string" && value2 ? value2 : void 0;
37951
38232
  }
@@ -37954,9 +38235,9 @@ function normalizeMachineRows(rows, candidateKeys) {
37954
38235
  const metadata = decryptJsonWithCandidates(row2.metadata, candidateKeys);
37955
38236
  return {
37956
38237
  id: row2.id,
37957
- host: readString$2(metadata, "host") ?? row2.id.slice(0, 8),
37958
- platform: readString$2(metadata, "platform") ?? "unknown",
37959
- ...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") } : {},
37960
38241
  online: typeof row2.online === "boolean" ? row2.online : row2.active === true,
37961
38242
  // Unknown last-seen renders as "unknown", never as "just now" — same
37962
38243
  // reasoning as src/api/client.ts#normalizeMachine.
@@ -40307,7 +40588,7 @@ async function startFrontDoor(options) {
40307
40588
  return;
40308
40589
  }
40309
40590
  const target = upstreams[route];
40310
- const upstream = request({
40591
+ const upstream = request$1({
40311
40592
  host: target.hostname,
40312
40593
  port: target.port,
40313
40594
  method: req.method,
@@ -40676,9 +40957,9 @@ function serverLogWriter(layout, name) {
40676
40957
  };
40677
40958
  }
40678
40959
  const happyCliPackageName = "@buzzni/happy-cli";
40679
- const happyCliVersion = "1.1.10-aplus.317";
40680
- const happyCliTag = "happy-cli-v1.1.10-aplus.317";
40681
- const vendorCommit = "d5fcae31b2fd14942813f1937a2e141c39b6d412";
40960
+ const happyCliVersion = "1.1.10-aplus.319";
40961
+ const happyCliTag = "happy-cli-v1.1.10-aplus.319";
40962
+ const vendorCommit = "4cc930893e9530c5d88ab0280575979b433738a3";
40682
40963
  const saycodeCliVersion = "0.8.2";
40683
40964
  const happyServerVersion = "0.1.2";
40684
40965
  const releaseFeatures = { "mcpRuntimeRecovery": { "enabled": true, "releaseState": "released", "sourceBranch": "main", "sourceHead": "a79a10e288cf7775cb15955f716693c6a7df1640", "requiredCommit": "975e32cf" } };
@@ -45856,7 +46137,7 @@ function requireLib$2() {
45856
46137
  }
45857
46138
  if (this.state !== PENDING) {
45858
46139
  var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
45859
- unwrap(promise, resolver, this.outcome);
46140
+ unwrap2(promise, resolver, this.outcome);
45860
46141
  } else {
45861
46142
  this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
45862
46143
  }
@@ -45877,15 +46158,15 @@ function requireLib$2() {
45877
46158
  handlers.resolve(this.promise, value2);
45878
46159
  };
45879
46160
  QueueItem.prototype.otherCallFulfilled = function(value2) {
45880
- unwrap(this.promise, this.onFulfilled, value2);
46161
+ unwrap2(this.promise, this.onFulfilled, value2);
45881
46162
  };
45882
46163
  QueueItem.prototype.callRejected = function(value2) {
45883
46164
  handlers.reject(this.promise, value2);
45884
46165
  };
45885
46166
  QueueItem.prototype.otherCallRejected = function(value2) {
45886
- unwrap(this.promise, this.onRejected, value2);
46167
+ unwrap2(this.promise, this.onRejected, value2);
45887
46168
  };
45888
- function unwrap(promise, func, value2) {
46169
+ function unwrap2(promise, func, value2) {
45889
46170
  immediate(function() {
45890
46171
  var returnValue;
45891
46172
  try {
@@ -54489,6 +54770,52 @@ const conversationTabsEn = {
54489
54770
  "workspaceHost.closeConversationHint": "Closes this tab. Your conversation history is kept.",
54490
54771
  "workspaceHost.newConversation": "Open a new conversation"
54491
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
+ };
54492
54819
  const questionComposerEn = {
54493
54820
  "askUser.skip": "Skip",
54494
54821
  "askUser.skipAnswer": "The user skipped answering this question.",
@@ -54710,6 +55037,12 @@ const projectSettingsEn = {
54710
55037
  "projectSettings.diagnostics": "Developer information"
54711
55038
  };
54712
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.",
54713
55046
  "aiRecovery.modelUnsupported": "This AI cannot use the selected model. Change the model in the message box, then retry.",
54714
55047
  "aiRecovery.accessSummary": "This account cannot use Claude.",
54715
55048
  "aiRecovery.connectAccount": "Reconnect AI",
@@ -54827,6 +55160,8 @@ const managedClaudeEn = {
54827
55160
  "managedClaude.loading": "Connecting AI…"
54828
55161
  };
54829
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.",
54830
55165
  "work.homeTitle": "What can I help you with?",
54831
55166
  "work.new": "Start new chat",
54832
55167
  "work.archived": "Archived chats",
@@ -54882,7 +55217,8 @@ const workEn = {
54882
55217
  "work.sourceNotice.denied": "You do not have access to this file. Check your permissions.",
54883
55218
  "work.sourceNotice.unavailable": "Could not check the file. Try again.",
54884
55219
  "work.saveCheck": "Check saved copy",
54885
- "work.checkingFile": "Checking…"
55220
+ "work.checkingFile": "Checking…",
55221
+ "work.mediaViewerNeeded": "Install a media preview extension and allow it to read the selected file."
54886
55222
  };
54887
55223
  const FILE_ACCESS_TRANSLATIONS_EN = {
54888
55224
  "fileAccess.retry": "Try again",
@@ -54958,6 +55294,8 @@ const organizationInstallRecoveryEn = {
54958
55294
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "The file could not run or report its version.",
54959
55295
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "Tool preparation failed. The exact cause is not yet known.",
54960
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.",
54961
55299
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "Retry, or install the tool using the guide and check again.",
54962
55300
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "Prepare the listed prerequisite first, then retry.",
54963
55301
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "Check the internet, company network and proxy, then retry.",
@@ -54970,12 +55308,55 @@ const organizationInstallRecoveryEn = {
54970
55308
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "Check the network and retry. Share this diagnostic if it repeats.",
54971
55309
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "Check installation completeness and whether company policy permits execution.",
54972
55310
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "Retry. If it repeats, share this diagnostic.",
54973
- "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."
54974
55353
  };
54975
55354
  const organizationStartEn = {
54976
55355
  "organizationStart.toolsDetails": "Tool preparation details",
54977
55356
  "organizationStart.toolsProgress": "Tool preparation status",
54978
55357
  ...organizationInstallRecoveryEn,
55358
+ ...organizationAccountKeyEn,
55359
+ ...organizationPcRegistrationEn,
54979
55360
  "organizationStart.connectingTitle": "Connecting to your organization",
54980
55361
  "organizationStart.connectingBody": "Getting ready to work in {name}.",
54981
55362
  "organizationStart.welcome": "Start working with your organization",
@@ -55003,6 +55384,9 @@ const organizationStartEn = {
55003
55384
  "organizationStart.aiGroupConflict": "Your organization’s AI account settings need attention. Contact your organization administrator.",
55004
55385
  "organizationStart.aiChecking": "Connecting AI…",
55005
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",
55006
55390
  "organizationStart.existingConnection": "Select Next to connect this PC to {name}.",
55007
55391
  "organizationStart.failed": "Onboarding setup",
55008
55392
  "organizationStart.recovery.title": "The app needs repair",
@@ -55109,7 +55493,32 @@ const organizationStartEn = {
55109
55493
  "organizationStart.diagnostic.reason.registrationChanged": "This PC's organization registration was changed or deleted, so it cannot connect with this account.",
55110
55494
  "organizationStart.diagnostic.action.registrationChanged": "Ask your organization administrator to check this PC's registration under Organization admin → PCs, then try again.",
55111
55495
  "organizationStart.diagnostic.reason.publicVerify": "Shared credentials were applied, but usable AI authentication was not confirmed.",
55112
- "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."
55113
55522
  };
55114
55523
  const usageEntryEn = {
55115
55524
  ...selfHostConnectEn,
@@ -55249,7 +55658,7 @@ const usageEntryEn = {
55249
55658
  "localTools.prerequisitesBody": "Check uv, Git and Node.js (including npm). Check again after installing.",
55250
55659
  "localTools.prerequisitesTitle": "Development prerequisites",
55251
55660
  "onboarding.preparationComplete": "Required setup complete",
55252
- "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.",
55253
55662
  "onboarding.additionalStep": "Additional step",
55254
55663
  "onboarding.requiredSettings": "Required setup",
55255
55664
  "onboarding.additionalSettings": "Additional setup — optional",
@@ -55332,6 +55741,11 @@ const projectRelocationEN = {
55332
55741
  "sessionCreation.workspaceNotSet": "This machine’s workspace path is not set. Please set the machine workspace first."
55333
55742
  };
55334
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.",
55335
55749
  "chatSchedules.olderReplies": "Earlier replies",
55336
55750
  "chatSchedules.newerReplies": "Newer replies",
55337
55751
  "chatSchedules.limitedReplies": "Showing part of this run. Open the conversation to read the full replies.",
@@ -55777,7 +56191,12 @@ const machineUsageEn = {
55777
56191
  "machineUsage.autoSwitched": "Automatically switched {provider}: {from} → {to}",
55778
56192
  "machineUsage.pinned": "pinned",
55779
56193
  "machineUsage.codexAccounts": "Codex accounts",
55780
- "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."
55781
56200
  };
55782
56201
  const machineResourceMetricsEn = {
55783
56202
  "machineResourceMetrics.title": "Session machine CPU and memory",
@@ -56192,7 +56611,7 @@ const outboundWebhookEn = {
56192
56611
  };
56193
56612
  const messengerChannelsEn = {
56194
56613
  "settings.messengerChannels.title": "Messenger channels",
56195
- "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.",
56196
56615
  "settings.messengerChannels.alwaysOnTitle": "Keep channels connected after quitting",
56197
56616
  "settings.messengerChannels.alwaysOnHelp": "Receive messages and continue work after quitting Saycode. Your computer must be awake and connected to the internet.",
56198
56617
  "settings.messengerChannels.alwaysOnBlocked": "Not available: this device cannot store the bot credential securely, so nothing could receive once Desktop exits.",
@@ -56350,7 +56769,7 @@ const messengerQuickSetupEn = {
56350
56769
  "settings.messengerChannels.quickSlack.openInSlack": "Open in Slack",
56351
56770
  "settings.messengerChannels.quickSlack.waiting": "Once you send it in Slack, the connection request appears here. Allow it and you are done.",
56352
56771
  "settings.messengerChannels.quickSlack.pairedWith": "Connected to {name}'s Slack",
56353
- "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.",
56354
56773
  "settings.messengerChannels.quickDiscord.title": "Connect Discord",
56355
56774
  "settings.messengerChannels.quickDiscord.subtitle": "Hand work to Saycode from Discord. Two steps and you are done.",
56356
56775
  "settings.messengerChannels.quickDiscord.installTitle": "Discord is not turned on yet",
@@ -56655,6 +57074,9 @@ const extensionPanelEn = {
56655
57074
  const extensionInstallEn = {
56656
57075
  "extensions.installOfficial": "Install extension",
56657
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}",
56658
57080
  "extensions.officialName.artifactPublisher": "Public link extension",
56659
57081
  "extensions.officialName.pluginManager": "Plugin manager extension",
56660
57082
  "extensions.officialName.projectTemplates": "Project templates extension",
@@ -56683,25 +57105,26 @@ const extensionInstallEn = {
56683
57105
  "artifactPublication.error.network": "Could not reach the server. Check your network connection.",
56684
57106
  "artifactPublication.error.unauthenticated": "Your session expired. Sign in again and retry.",
56685
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.",
56686
57109
  "artifactPublication.error.tooLarge": "The file is too large. A published HTML file must be 10MB or smaller.",
56687
57110
  "artifactPublication.error.rateLimited": "Too many public link requests. Please try again in a moment.",
56688
57111
  "artifactPublication.error.rejected": "The server rejected the request to publish this file.",
56689
57112
  "artifactPublication.error.serverError": "The server hit an error. Please try again in a moment.",
56690
57113
  "artifactPublication.error.extensionUnavailable": "The extension is disabled or missing the publish permission. Check Artifact Publisher in settings.",
56691
- "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."
56692
57116
  };
56693
57117
  const artifactLibraryTranslations$2 = {
56694
57118
  "artifactLibrary.title": "Artifacts",
56695
57119
  "artifactLibrary.scope": "Scope",
56696
57120
  "artifactLibrary.conversation": "This conversation",
56697
57121
  "artifactLibrary.project": "This project",
56698
- "artifactLibrary.all": "All projects",
56699
57122
  "artifactLibrary.search": "Search files, projects, conversations and paths",
56700
- "artifactLibrary.hint": "Documents and previews from conversations. Opens the original file.",
56701
57123
  "artifactLibrary.loading": "Finding artifacts…",
56702
57124
  "artifactLibrary.error": "Some conversations could not be loaded. Please retry.",
56703
57125
  "artifactLibrary.empty": "No artifacts yet.",
56704
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.",
56705
57128
  "artifactLibrary.retry": "Retry"
56706
57129
  };
56707
57130
  const personalChatsLandingEn = {
@@ -57241,7 +57664,39 @@ const projectScopeEn = {
57241
57664
  "projectScope.grantRevoked": "File access was revoked and the session restarted.",
57242
57665
  "projectScope.daemonUpgradeRequired": "Update Aplus CLI, then try again."
57243
57666
  };
57667
+ const machineSettingsLabelsEn = {
57668
+ "machineSettings.listRefreshed": "Machine list refreshed.",
57669
+ "machineSettings.codeGenerated": "Registration code generated.",
57670
+ "machineSettings.nameSaved": "Machine name saved.",
57671
+ "machineSettings.nameCleared": "Machine name cleared.",
57672
+ "machineSettings.nameSaveFailed": "Could not save the machine name.",
57673
+ "machineSettings.removed": "{name} was removed.",
57674
+ "machineSettings.zrokStopped": "zrok share stopped.",
57675
+ "machineSettings.zrokStart": "Start zrok",
57676
+ "machineSettings.zrokStarting": "Starting zrok",
57677
+ "machineSettings.zrokStop": "Stop zrok",
57678
+ "machineSettings.externalServerUrl": "External server URL",
57679
+ "machineSettings.externalServerUrlPlaceholder": "zrok URL for {url}",
57680
+ "machineSettings.liveModeOnly": "Machine registration and deletion are available in live mode.",
57681
+ "machineSettings.issuedCodes": "Issued codes",
57682
+ "machineSettings.generateCode": "Generate code",
57683
+ "machineSettings.registrationCode": "Registration code",
57684
+ "machineSettings.expiresAt": "Expires {time}",
57685
+ "machineSettings.expired": "Expired",
57686
+ "machineSettings.copyCommand": "Copy command",
57687
+ "machineSettings.commandCopied": "Copied",
57688
+ "machineSettings.loadingCodes": "Loading codes…",
57689
+ "machineSettings.loadingMachines": "Loading machines…",
57690
+ "machineSettings.emptyTitle": "No machines",
57691
+ "machineSettings.emptyOrganization": "No machines are available for the selected organization.",
57692
+ "machineSettings.emptyWorkspace": "No machines are available in the current workspace.",
57693
+ "machineSettings.rename": "Rename",
57694
+ "machineSettings.delete": "Delete",
57695
+ "machineSettings.confirmDelete": "Confirm delete",
57696
+ "machineSettings.organization": "Organization"
57697
+ };
57244
57698
  const machineSettingsConsoleEn = {
57699
+ ...machineSettingsLabelsEn,
57245
57700
  "machineConsole.showDetails": "Show machine details",
57246
57701
  "machineConsole.showDetailsHint": "Show details when hovering over or focusing a machine in the machine picker.",
57247
57702
  "machineConsole.environmentStatus": "Environment",
@@ -57533,9 +57988,15 @@ const sidebarUpdateEn = {
57533
57988
  "sidebarUpdate.completedBody": "Saycode v{version} has been updated.",
57534
57989
  "sidebarUpdate.installing": "Applying update…",
57535
57990
  "sidebarUpdate.update": "Update",
57536
- "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.",
57537
57992
  "sidebarUpdate.minimize": "Minimize",
57538
- "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"
57539
58000
  };
57540
58001
  const globalPauseEn = {
57541
58002
  "settings.globalPause.enable": "Global pause (block new automatic actions)",
@@ -58860,7 +59321,6 @@ const onboardingEn = {
58860
59321
  "onboarding.page.addProject": "Add your first project",
58861
59322
  "onboarding.page.configured": "Configured",
58862
59323
  "onboarding.page.optional": "Optional",
58863
- "onboarding.page.computerReveal": "Internal test: show computer and browser control",
58864
59324
  "onboarding.page.testNotification": "Send test notification",
58865
59325
  "onboarding.page.testNotificationSending": "Sending test notification…",
58866
59326
  "onboarding.page.testNotificationSuccess": "Test notification sent.",
@@ -59035,6 +59495,7 @@ const en = {
59035
59495
  "aiModelPolicy.machineChanged": "Chat creation stopped because the selected PC is unavailable. Check the PC before sending again.",
59036
59496
  "aiModelPolicy.policyChanged": "Chat creation stopped because the allowed model policy changed. Review your question and attachments before sending again.",
59037
59497
  "aiModelPolicy.noModels": "No allowed models are available on this computer.",
59498
+ ...personalAiEn,
59038
59499
  ...conversationTabsEn,
59039
59500
  ...questionComposerEn,
59040
59501
  ...aiRecoveryEn,
@@ -60949,6 +61410,7 @@ const en = {
60949
61410
  "runtimeEvent.claudeNotFound": "Claude Code could not start: its executable could not be found on this machine. Reinstall happy-cli on it.",
60950
61411
  "runtimeEvent.claudeStoppedBySignal": "Claude Code could not start: it stopped unexpectedly ({signal}). Check this machine's daemon log.",
60951
61412
  "runtimeEvent.claudeCouldNotStart": "Claude Code could not start on this machine. Check this machine's daemon log for details.",
61413
+ "runtimeEvent.claudeNotSignedIn": "Claude Code is not signed in on this machine. Sign in as the user that runs this machine's daemon (run `claude`, then /login) and send the message again.",
60952
61414
  "runtimeEvent.compactionCompleted": "Finished summarizing. The conversation continues from that summary.",
60953
61415
  "runtimeEvent.codexError": "Codex error: {detail}",
60954
61416
  "runtimeEvent.codexNoImages": "There were no images that could be sent to Codex.",
@@ -61291,6 +61753,9 @@ const en = {
61291
61753
  "projectGitView.branchRename": "Rename branch",
61292
61754
  "projectGitView.branchDelete": "Delete branch",
61293
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.",
61294
61759
  "terminal.connectFailed": "Failed to connect to the terminal.",
61295
61760
  "terminal.cwdPrepFailed": "Failed to prepare the remote terminal working directory.",
61296
61761
  "terminal.restoredOutputDivider": "Previous session output",
@@ -61792,6 +62257,52 @@ const conversationTabsZh = {
61792
62257
  "workspaceHost.closeConversationHint": "仅关闭标签页,对话记录会保留。",
61793
62258
  "workspaceHost.newConversation": "打开新对话"
61794
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
+ };
61795
62306
  const questionComposerZh = {
61796
62307
  "askUser.skip": "跳过",
61797
62308
  "askUser.skipAnswer": "用户跳过了这个问题的回答。",
@@ -62013,6 +62524,12 @@ const projectSettingsZh = {
62013
62524
  "projectSettings.diagnostics": "开发信息"
62014
62525
  };
62015
62526
  const aiRecoveryZh = {
62527
+ "aiRecovery.capacityTitle": "模型繁忙重试",
62528
+ "aiRecovery.capacitySummary": "当前请求较多。请稍后重试。",
62529
+ "aiRecovery.capacityCountdown": "{seconds}秒后重试 ({attempt}/3)",
62530
+ "aiRecovery.capacityCancel": "取消",
62531
+ "aiRecovery.capacityExhausted": "自动重试已结束。请稍后重试或在输入框中更换模型。",
62532
+ "aiRecovery.capacityModelHint": "可以在输入框中选择其他模型。",
62016
62533
  "aiRecovery.modelUnsupported": "当前 AI 无法使用所选模型。请在输入框中更换模型后重试。",
62017
62534
  "aiRecovery.accessSummary": "此账号无法使用Claude。",
62018
62535
  "aiRecovery.connectAccount": "重新连接AI",
@@ -62130,6 +62647,8 @@ const managedClaudeZh = {
62130
62647
  "managedClaude.loading": "正在连接AI…"
62131
62648
  };
62132
62649
  const workZh = {
62650
+ "work.mediaChanged": "原文件已更改。请重新打开。",
62651
+ "work.mediaUnsupported": "此计算机不支持媒体流式播放。请保存原文件并在本地打开。",
62133
62652
  "work.homeTitle": "需要我帮您完成什么工作?",
62134
62653
  "work.new": "开始新chat",
62135
62654
  "work.archived": "已归档chat",
@@ -62185,7 +62704,8 @@ const workZh = {
62185
62704
  "work.sourceNotice.denied": "您无权访问此文件。请检查权限。",
62186
62705
  "work.sourceNotice.unavailable": "无法检查文件。请重试。",
62187
62706
  "work.saveCheck": "检查保存的副本",
62188
- "work.checkingFile": "检查中…"
62707
+ "work.checkingFile": "检查中…",
62708
+ "work.mediaViewerNeeded": "请安装媒体预览扩展,并允许其读取所选文件。"
62189
62709
  };
62190
62710
  const FILE_ACCESS_TRANSLATIONS_ZH = {
62191
62711
  "fileAccess.retry": "重试",
@@ -62261,6 +62781,8 @@ const organizationInstallRecoveryZh = {
62261
62781
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "无法运行文件或检查版本。",
62262
62782
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "工具准备失败,具体原因尚未确认。",
62263
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": "公司代理不允许连接下载或软件包服务器。",
62264
62786
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "请重试,或按指南安装后再次检查。",
62265
62787
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "请先准备所列前置工具,然后重试。",
62266
62788
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "请检查互联网、公司网络和代理,然后重试。",
@@ -62273,12 +62795,55 @@ const organizationInstallRecoveryZh = {
62273
62795
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "请检查网络后重试;若重复发生,请提供诊断。",
62274
62796
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "请检查安装完整性及公司策略是否允许执行。",
62275
62797
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "请重试;若重复发生,请提供此诊断。",
62276
- "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": "请联系组织管理员重新注册这台电脑。"
62277
62840
  };
62278
62841
  const organizationStartZh = {
62279
62842
  "organizationStart.toolsDetails": "工具准备详情",
62280
62843
  "organizationStart.toolsProgress": "工具准备状态",
62281
62844
  ...organizationInstallRecoveryZh,
62845
+ ...organizationAccountKeyZh,
62846
+ ...organizationPcRegistrationZh,
62282
62847
  "organizationStart.connectingTitle": "正在连接组织",
62283
62848
  "organizationStart.connectingBody": "正在准备进入{name}开展工作。",
62284
62849
  "organizationStart.welcome": "立即在组织中开始",
@@ -62306,6 +62871,9 @@ const organizationStartZh = {
62306
62871
  "organizationStart.aiGroupConflict": "需要检查组织的AI账号设置。请联系组织管理员。",
62307
62872
  "organizationStart.aiChecking": "正在连接AI…",
62308
62873
  "organizationStart.connectLater": "稍后连接",
62874
+ "organizationStart.skip": "跳过",
62875
+ "organizationStart.noAi.body": "{name} 尚未注册组织 AI 账号。可以跳过此步骤,使用自己的 AI 账号开始。",
62876
+ "organizationStart.noAi.status": "没有组织 AI 账号",
62309
62877
  "organizationStart.existingConnection": "要将此电脑连接到{name},请点击“下一步”。",
62310
62878
  "organizationStart.failed": "入门准备",
62311
62879
  "organizationStart.recovery.title": "需要修复应用",
@@ -62412,7 +62980,32 @@ const organizationStartZh = {
62412
62980
  "organizationStart.diagnostic.reason.registrationChanged": "此电脑的组织登记已被更改或删除,无法用此账号连接。",
62413
62981
  "organizationStart.diagnostic.action.registrationChanged": "请联系组织管理员在 组织管理 → 电脑 中检查此电脑的登记,然后重试。",
62414
62982
  "organizationStart.diagnostic.reason.publicVerify": "共享认证已应用,但未能确认 AI 认证可用。",
62415
- "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": "无法刷新邀请,请重新检查。"
62416
63009
  };
62417
63010
  const usageEntryZh = {
62418
63011
  ...selfHostConnectZh,
@@ -62552,7 +63145,7 @@ const usageEntryZh = {
62552
63145
  "localTools.prerequisitesBody": "检查 uv、Git 和 Node.js(含 npm)的安装状态。安装后请重新检查。",
62553
63146
  "localTools.prerequisitesTitle": "基本开发工具",
62554
63147
  "onboarding.preparationComplete": "必需设置完成",
62555
- "onboarding.additionalStepsBody": "Saycode 指令、AI 账户、通知以及电脑与浏览器控制均为可选步骤。您可以立即开始,稍后再设置。",
63148
+ "onboarding.additionalStepsBody": "Saycode 指令、AI 账户和通知均为可选步骤。您可以立即开始,稍后再设置。",
62556
63149
  "onboarding.additionalStep": "附加步骤",
62557
63150
  "onboarding.requiredSettings": "必需设置",
62558
63151
  "onboarding.additionalSettings": "附加设置 — 可选",
@@ -62635,6 +63228,11 @@ const projectRelocationZH = {
62635
63228
  "sessionCreation.workspaceNotSet": "此机器的工作区路径尚未设置。请先设置机器工作区。"
62636
63229
  };
62637
63230
  const chatSchedulesZh = {
63231
+ "chatSchedules.chatTitle": "此对话的预约",
63232
+ "chatSchedules.sharedHint": "您也可以在预约菜单中查看和管理相同的预约。",
63233
+ "chatSchedules.allSchedules": "查看所有预约",
63234
+ "chatSchedules.advanced": "高级自动化设置",
63235
+ "chatSchedules.advancedHint": "设置 AI、执行条件和脚本自动化。AI 预约与上方列表共享,脚本自动化是另一种执行类型。",
62638
63236
  "chatSchedules.olderReplies": "较早的回答",
62639
63237
  "chatSchedules.newerReplies": "较新的回答",
62640
63238
  "chatSchedules.limitedReplies": "当前仅显示本次运行的部分回答。请打开对话查看完整回答。",
@@ -63080,7 +63678,12 @@ const machineUsageZh = {
63080
63678
  "machineUsage.autoSwitched": "已自动切换 {provider}:{from} → {to}",
63081
63679
  "machineUsage.pinned": "已固定",
63082
63680
  "machineUsage.codexAccounts": "Codex 账户",
63083
- "machineUsage.usageStale": "正在刷新用量"
63681
+ "machineUsage.usageStale": "正在刷新用量",
63682
+ "machineUsage.autoRotation": "自动轮换账户",
63683
+ "machineUsage.autoRotationOn": "接近限额时自动切换到其他账户。",
63684
+ "machineUsage.autoRotationOff": "继续使用所选账户。",
63685
+ "machineUsage.autoRotationPaused": "你手动选择了账户,已关闭自动轮换。",
63686
+ "machineUsage.autoRotationFailed": "无法更改自动轮换,请重试。"
63084
63687
  };
63085
63688
  const machineResourceMetricsZh = {
63086
63689
  "machineResourceMetrics.title": "会话机器的 CPU/内存",
@@ -63495,7 +64098,7 @@ const outboundWebhookZh = {
63495
64098
  };
63496
64099
  const messengerChannelsZh = {
63497
64100
  "settings.messengerChannels.title": "即时通讯渠道",
63498
- "settings.messengerChannels.privacyNotice": "仅会将请求的状态和最终答复发送到已连接的即时通讯工具 — 绝不发送原始工具输出、历史记录或附件。即时通讯传输不在 Saycode 端到端加密范围内。",
64101
+ "settings.messengerChannels.privacyNotice": "会将请求的状态和最终答复发送到已连接的即时通讯工具;在 Slack 中,还会发送代理在该答复中指定的项目文件夹内的文件。绝不发送原始工具输出或历史记录。即时通讯传输不在 Saycode 端到端加密范围内。",
63499
64102
  "settings.messengerChannels.alwaysOnTitle": "退出应用后保持连接",
63500
64103
  "settings.messengerChannels.alwaysOnHelp": "退出 Saycode 后仍可接收消息并继续工作。电脑必须保持唤醒并连接互联网。",
63501
64104
  "settings.messengerChannels.alwaysOnBlocked": "不可用:此设备无法安全存储机器人凭据,因此 Desktop 退出后将无法接收。",
@@ -63653,7 +64256,7 @@ const messengerQuickSetupZh = {
63653
64256
  "settings.messengerChannels.quickSlack.openInSlack": "在 Slack 中打开",
63654
64257
  "settings.messengerChannels.quickSlack.waiting": "在 Slack 中发送后,连接请求会显示在这里。允许即可完成。",
63655
64258
  "settings.messengerChannels.quickSlack.pairedWith": "已连接 {name} 的 Slack",
63656
- "settings.messengerChannels.quickSlack.connectedHelp": "在发送连接码的私信中直接和机器人对话即可。提及机器人后发送 /help 可查看命令。",
64259
+ "settings.messengerChannels.quickSlack.connectedHelp": "在发送连接码的私信中直接和机器人对话即可。在已邀请机器人的频道中提及它,它会在该消息的话题中回复(频道成员可见)。提及机器人后发送 /help 可查看命令。",
63657
64260
  "settings.messengerChannels.quickDiscord.title": "连接 Discord",
63658
64261
  "settings.messengerChannels.quickDiscord.subtitle": "在 Discord 中直接把工作交给 Saycode。两步即可完成。",
63659
64262
  "settings.messengerChannels.quickDiscord.installTitle": "Discord 功能尚未开启",
@@ -63958,6 +64561,9 @@ const extensionPanelZh = {
63958
64561
  const extensionInstallZh = {
63959
64562
  "extensions.installOfficial": "安装扩展",
63960
64563
  "extensions.installNamedOfficial": "安装 {name}",
64564
+ "extensions.officialUpdate": "官方更新到 {version}",
64565
+ "extensions.updateSaved": "新版本已安装,但无法确认后续处理已完成。请重新检查扩展状态。",
64566
+ "extensions.projectToolHint": "在项目对话右侧工作面板的更多(⋮)菜单中打开:{tools}",
63961
64567
  "extensions.officialName.artifactPublisher": "公开链接扩展",
63962
64568
  "extensions.officialName.pluginManager": "插件管理扩展",
63963
64569
  "extensions.officialName.projectTemplates": "项目模板扩展",
@@ -63986,25 +64592,26 @@ const extensionInstallZh = {
63986
64592
  "artifactPublication.error.network": "无法连接服务器。请检查网络连接。",
63987
64593
  "artifactPublication.error.unauthenticated": "登录会话已过期。请重新登录后再试。",
63988
64594
  "artifactPublication.error.notFound": "找不到文件。请确认文件已保存。",
64595
+ "artifactPublication.error.notFoundInFolder": "在工作文件夹 {folder} 中找不到 {file}。如果文件是在其他 worktree 中创建的,请将其移到此文件夹后重试。",
63989
64596
  "artifactPublication.error.tooLarge": "文件过大。公开的 HTML 文件必须小于等于 10MB。",
63990
64597
  "artifactPublication.error.rateLimited": "公开链接请求过多。请稍后重试。",
63991
64598
  "artifactPublication.error.rejected": "服务器拒绝了此文件的公开请求。",
63992
64599
  "artifactPublication.error.serverError": "服务器发生错误。请稍后重试。",
63993
64600
  "artifactPublication.error.extensionUnavailable": "扩展已禁用或缺少公开权限。请在设置中检查 Artifact Publisher。",
63994
- "artifactPublication.error.invalidResponse": "无法校验服务器响应,已中止。"
64601
+ "artifactPublication.error.invalidResponse": "无法校验服务器响应,已中止。",
64602
+ "extensions.permission.filePreviewsRead": "读取在应用中打开的文件的预览内容。源路径和凭据由 Saycode Core 管理。"
63995
64603
  };
63996
64604
  const artifactLibraryTranslations$1 = {
63997
64605
  "artifactLibrary.title": "成果汇总",
63998
64606
  "artifactLibrary.scope": "范围",
63999
64607
  "artifactLibrary.conversation": "当前对话",
64000
64608
  "artifactLibrary.project": "当前项目",
64001
- "artifactLibrary.all": "所有项目",
64002
64609
  "artifactLibrary.search": "搜索文件、项目、对话和路径",
64003
- "artifactLibrary.hint": "汇总对话中的文档和预览。打开原始文件。",
64004
64610
  "artifactLibrary.loading": "正在查找成果…",
64005
64611
  "artifactLibrary.error": "部分对话加载失败。请重试。",
64006
64612
  "artifactLibrary.empty": "暂无成果。",
64007
64613
  "artifactLibrary.noMatches": "没有匹配的成果。",
64614
+ "artifactLibrary.readUnsupported": "此电脑不支持打开工作成果原文件。目前此功能支持 macOS 工作电脑。",
64008
64615
  "artifactLibrary.retry": "重试"
64009
64616
  };
64010
64617
  const personalChatsLandingZh = {
@@ -64544,7 +65151,39 @@ const projectScopeZh = {
64544
65151
  "projectScope.grantRevoked": "文件访问权限已撤销,会话已重新启动。",
64545
65152
  "projectScope.daemonUpgradeRequired": "请更新 Aplus CLI 后重试。"
64546
65153
  };
65154
+ const machineSettingsLabelsZh = {
65155
+ "machineSettings.listRefreshed": "已刷新电脑列表。",
65156
+ "machineSettings.codeGenerated": "已生成注册码。",
65157
+ "machineSettings.nameSaved": "已保存电脑名称。",
65158
+ "machineSettings.nameCleared": "已清除电脑名称。",
65159
+ "machineSettings.nameSaveFailed": "无法保存电脑名称。",
65160
+ "machineSettings.removed": "已删除 {name}。",
65161
+ "machineSettings.zrokStopped": "已停止 zrok 共享。",
65162
+ "machineSettings.zrokStart": "启动 zrok",
65163
+ "machineSettings.zrokStarting": "正在启动 zrok",
65164
+ "machineSettings.zrokStop": "停止 zrok",
65165
+ "machineSettings.externalServerUrl": "外部服务器地址",
65166
+ "machineSettings.externalServerUrlPlaceholder": "{url} 的 zrok 地址",
65167
+ "machineSettings.liveModeOnly": "只能在实时模式下注册和删除电脑。",
65168
+ "machineSettings.issuedCodes": "已发放的代码",
65169
+ "machineSettings.generateCode": "生成注册码",
65170
+ "machineSettings.registrationCode": "注册码",
65171
+ "machineSettings.expiresAt": "{time} 过期",
65172
+ "machineSettings.expired": "已过期",
65173
+ "machineSettings.copyCommand": "复制命令",
65174
+ "machineSettings.commandCopied": "已复制",
65175
+ "machineSettings.loadingCodes": "正在加载代码…",
65176
+ "machineSettings.loadingMachines": "正在加载电脑…",
65177
+ "machineSettings.emptyTitle": "没有电脑",
65178
+ "machineSettings.emptyOrganization": "所选组织中没有可用的电脑。",
65179
+ "machineSettings.emptyWorkspace": "当前工作空间中没有可用的电脑。",
65180
+ "machineSettings.rename": "重命名",
65181
+ "machineSettings.delete": "删除",
65182
+ "machineSettings.confirmDelete": "确认删除",
65183
+ "machineSettings.organization": "组织"
65184
+ };
64547
65185
  const machineSettingsConsoleZh = {
65186
+ ...machineSettingsLabelsZh,
64548
65187
  "machineConsole.showDetails": "显示机器详细信息",
64549
65188
  "machineConsole.showDetailsHint": "在机器选择列表中悬停或用键盘聚焦时显示详细信息。",
64550
65189
  "machineConsole.environmentStatus": "环境·状态",
@@ -64836,9 +65475,15 @@ const sidebarUpdateZh = {
64836
65475
  "sidebarUpdate.completedBody": "Saycode 已更新至 v{version}。",
64837
65476
  "sidebarUpdate.installing": "正在应用更新…",
64838
65477
  "sidebarUpdate.update": "更新",
64839
- "sidebarUpdate.restartNotice": "重启前将保存对话状态。",
65478
+ "sidebarUpdate.restartNotice": "重启前将保存对话状态。安装完成后 Saycode 会自动重新打开。",
64840
65479
  "sidebarUpdate.minimize": "最小化",
64841
- "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": "退出"
64842
65487
  };
64843
65488
  const globalPauseZh = {
64844
65489
  "settings.globalPause.enable": "全局暂停(阻止新的自动操作)",
@@ -66163,7 +66808,6 @@ const onboardingZh = {
66163
66808
  "onboarding.page.addProject": "添加第一个项目",
66164
66809
  "onboarding.page.configured": "已设置",
66165
66810
  "onboarding.page.optional": "可选",
66166
- "onboarding.page.computerReveal": "内部测试:显示电脑和浏览器控制",
66167
66811
  "onboarding.page.testNotification": "发送测试通知",
66168
66812
  "onboarding.page.testNotificationSending": "正在发送测试通知…",
66169
66813
  "onboarding.page.testNotificationSuccess": "测试通知已发送。",
@@ -66338,6 +66982,7 @@ const zh = {
66338
66982
  "aiModelPolicy.machineChanged": "所选电脑不可用,已停止创建对话。请检查电脑后重新发送。",
66339
66983
  "aiModelPolicy.policyChanged": "允许模型的策略已更改,已停止创建对话。请检查问题和附件后重新发送。",
66340
66984
  "aiModelPolicy.noModels": "此计算机没有可选择的获准模型。",
66985
+ ...personalAiZh,
66341
66986
  ...conversationTabsZh,
66342
66987
  ...questionComposerZh,
66343
66988
  ...aiRecoveryZh,
@@ -68219,6 +68864,7 @@ const zh = {
68219
68864
  "runtimeEvent.claudeNotFound": "无法启动 Claude Code:在这台机器上找不到可执行文件。请在这台机器上重新安装 happy-cli。",
68220
68865
  "runtimeEvent.claudeStoppedBySignal": "无法启动 Claude Code:它意外停止了({signal})。请查看这台机器的 daemon 日志。",
68221
68866
  "runtimeEvent.claudeCouldNotStart": "无法在这台机器上启动 Claude Code。详情请查看这台机器的 daemon 日志。",
68867
+ "runtimeEvent.claudeNotSignedIn": "这台机器上的 Claude Code 尚未登录。请以运行这台机器 daemon 的用户登录(运行 `claude` 后执行 /login),然后重新发送消息。",
68222
68868
  "runtimeEvent.compactionCompleted": "对话摘要已完成,之后将以该摘要作为上下文继续。",
68223
68869
  "runtimeEvent.codexError": "Codex 错误:{detail}",
68224
68870
  "runtimeEvent.codexNoImages": "没有可发送给 Codex 的图片。",
@@ -68561,6 +69207,9 @@ const zh = {
68561
69207
  "projectGitView.branchRename": "重命名分支",
68562
69208
  "projectGitView.branchDelete": "删除分支",
68563
69209
  "terminal.connectTimeout": "终端连接超时。",
69210
+ "terminal.strictMachineUnsupported": "安全强化(strict)机器暂不支持远程终端。请直接在该机器上使用终端。",
69211
+ "terminal.machineControlUnknown": "该机器的 CLI 未报告其安全模式,因此无法打开远程终端。请在该机器上更新 happy CLI。",
69212
+ "terminal.machineKeyUnavailable": "无法打开该机器的密钥,因此未打开远程终端。请解锁账户密钥后重试。",
68564
69213
  "terminal.connectFailed": "无法连接到终端。",
68565
69214
  "terminal.cwdPrepFailed": "无法准备远程终端的工作目录。",
68566
69215
  "terminal.restoredOutputDivider": "上一次会话输出",
@@ -69095,6 +69744,52 @@ const conversationTabsJa = {
69095
69744
  "workspaceHost.closeConversationHint": "タブだけを閉じます。会話の履歴は残ります。",
69096
69745
  "workspaceHost.newConversation": "新しい会話を開く"
69097
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
+ };
69098
69793
  const questionComposerJa = {
69099
69794
  "askUser.skip": "スキップ",
69100
69795
  "askUser.skipAnswer": "ユーザーはこの質問への回答をスキップしました。",
@@ -69316,6 +70011,12 @@ const projectSettingsJa = {
69316
70011
  "projectSettings.diagnostics": "開発情報"
69317
70012
  };
69318
70013
  const aiRecoveryJa = {
70014
+ "aiRecovery.capacityTitle": "モデル混雑時の再試行",
70015
+ "aiRecovery.capacitySummary": "現在リクエストが集中しています。しばらくしてからもう一度お試しください。",
70016
+ "aiRecovery.capacityCountdown": "{seconds}秒後に再試行 ({attempt}/3)",
70017
+ "aiRecovery.capacityCancel": "キャンセル",
70018
+ "aiRecovery.capacityExhausted": "自動再試行が終了しました。後でもう一度試すか、入力欄でモデルを変更してください。",
70019
+ "aiRecovery.capacityModelHint": "入力欄で別のモデルに変更できます。",
69319
70020
  "aiRecovery.modelUnsupported": "このAIでは選択したモデルを使用できません。入力欄でモデルを変更してから再試行してください。",
69320
70021
  "aiRecovery.accessSummary": "このアカウントではClaudeを使用できません。",
69321
70022
  "aiRecovery.connectAccount": "AIに再接続",
@@ -69433,6 +70134,8 @@ const managedClaudeJa = {
69433
70134
  "managedClaude.loading": "AIに接続中…"
69434
70135
  };
69435
70136
  const workJa = {
70137
+ "work.mediaChanged": "元のファイルが変更されました。もう一度開いてください。",
70138
+ "work.mediaUnsupported": "このコンピューターではメディアストリーミングを利用できません。元のファイルを保存して開いてください。",
69436
70139
  "work.homeTitle": "どんな作業をお手伝いしましょうか?",
69437
70140
  "work.new": "新しいchatを開始",
69438
70141
  "work.archived": "アーカイブ済みのchat",
@@ -69488,7 +70191,8 @@ const workJa = {
69488
70191
  "work.sourceNotice.denied": "ファイルへのアクセス権がありません。権限を確認してください。",
69489
70192
  "work.sourceNotice.unavailable": "ファイルを確認できませんでした。再試行してください。",
69490
70193
  "work.saveCheck": "保存ファイルを再確認",
69491
- "work.checkingFile": "確認中…"
70194
+ "work.checkingFile": "確認中…",
70195
+ "work.mediaViewerNeeded": "メディアプレビュー拡張機能をインストールし、選択したファイルの読み取りを許可してください。"
69492
70196
  };
69493
70197
  const FILE_ACCESS_TRANSLATIONS_JA = {
69494
70198
  "fileAccess.retry": "再試行",
@@ -69564,6 +70268,8 @@ const organizationInstallRecoveryJa = {
69564
70268
  "organizationStart.toolDiagnostic.reason.TOOL_EXECUTION_FAILED": "ファイルの実行またはバージョン確認ができませんでした。",
69565
70269
  "organizationStart.toolDiagnostic.reason.TOOL_INSTALL_FAILED": "ツールの準備に失敗しました。原因はまだ確認できていません。",
69566
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": "会社のプロキシがダウンロードまたはパッケージサーバーへの接続を許可しませんでした。",
69567
70273
  "organizationStart.toolDiagnostic.action.TOOL_NOT_FOUND": "再試行するか、ガイドに従ってインストール後に再確認してください。",
69568
70274
  "organizationStart.toolDiagnostic.action.TOOL_PREREQUISITE_REQUIRED": "表示された前提ツールを準備してから再試行してください。",
69569
70275
  "organizationStart.toolDiagnostic.action.TOOL_NETWORK_FAILED": "インターネット・社内ネットワーク・プロキシを確認して再試行してください。",
@@ -69576,12 +70282,55 @@ const organizationInstallRecoveryJa = {
69576
70282
  "organizationStart.toolDiagnostic.action.TOOL_TIMEOUT": "ネットワークを確認して再試行してください。繰り返す場合は診断を共有してください。",
69577
70283
  "organizationStart.toolDiagnostic.action.TOOL_EXECUTION_FAILED": "インストールの完全性と社内ポリシーの実行許可を確認してください。",
69578
70284
  "organizationStart.toolDiagnostic.action.TOOL_INSTALL_FAILED": "再試行してください。繰り返す場合は診断を共有してください。",
69579
- "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を新しく登録するよう依頼してください。"
69580
70327
  };
69581
70328
  const organizationStartJa = {
69582
70329
  "organizationStart.toolsDetails": "ツール準備の詳細",
69583
70330
  "organizationStart.toolsProgress": "ツール準備状況",
69584
70331
  ...organizationInstallRecoveryJa,
70332
+ ...organizationAccountKeyJa,
70333
+ ...organizationPcRegistrationJa,
69585
70334
  "organizationStart.connectingTitle": "組織に接続中",
69586
70335
  "organizationStart.connectingBody": "{name}で作業を始める準備をしています。",
69587
70336
  "organizationStart.welcome": "組織ですぐに始めましょう",
@@ -69609,6 +70358,9 @@ const organizationStartJa = {
69609
70358
  "organizationStart.aiGroupConflict": "組織のAIアカウント設定の確認が必要です。組織の管理者にお知らせください。",
69610
70359
  "organizationStart.aiChecking": "AI接続中…",
69611
70360
  "organizationStart.connectLater": "後で接続",
70361
+ "organizationStart.skip": "スキップ",
70362
+ "organizationStart.noAi.body": "{name}には組織のAIアカウントが登録されていません。スキップして自分のAIアカウントで始められます。",
70363
+ "organizationStart.noAi.status": "組織のAIアカウントなし",
69612
70364
  "organizationStart.existingConnection": "このPCを{name}に接続するには「次へ」を押してください。",
69613
70365
  "organizationStart.failed": "オンボーディングの準備",
69614
70366
  "organizationStart.recovery.title": "アプリの修復が必要です",
@@ -69715,7 +70467,32 @@ const organizationStartJa = {
69715
70467
  "organizationStart.diagnostic.reason.registrationChanged": "このPCの組織登録が変更または削除されたため、このアカウントで接続できません。",
69716
70468
  "organizationStart.diagnostic.action.registrationChanged": "組織管理者に、組織管理 → PC でこのPCの登録を確認するよう依頼してから、もう一度お試しください。",
69717
70469
  "organizationStart.diagnostic.reason.publicVerify": "共有認証を適用しましたが、AI認証の利用を確認できませんでした。",
69718
- "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": "招待一覧を確認できませんでした。再確認してください。"
69719
70496
  };
69720
70497
  const usageEntryJa = {
69721
70498
  ...selfHostConnectJa,
@@ -69855,7 +70632,7 @@ const usageEntryJa = {
69855
70632
  "localTools.prerequisitesBody": "uv・Git・Node.js(npm を含む)のインストール状況を確認します。インストール後に再確認してください。",
69856
70633
  "localTools.prerequisitesTitle": "基本開発ツール",
69857
70634
  "onboarding.preparationComplete": "必須設定完了",
69858
- "onboarding.additionalStepsBody": "Saycodeの指示、AIアカウント、通知、コンピューター・ブラウザー操作は任意です。すぐに開始して、後で設定できます。",
70635
+ "onboarding.additionalStepsBody": "Saycodeの指示、AIアカウント、通知は任意です。すぐに開始して、後で設定できます。",
69859
70636
  "onboarding.additionalStep": "追加ステップ",
69860
70637
  "onboarding.requiredSettings": "必須設定",
69861
70638
  "onboarding.additionalSettings": "追加設定 — 任意",
@@ -69938,6 +70715,11 @@ const projectRelocationJA = {
69938
70715
  "sessionCreation.workspaceNotSet": "このマシンのワークスペースパスが設定されていません。先にマシンのワークスペースを設定してください。"
69939
70716
  };
69940
70717
  const chatSchedulesJa = {
70718
+ "chatSchedules.chatTitle": "このチャットの予約",
70719
+ "chatSchedules.sharedHint": "予約メニューでも同じ予約を確認・管理できます。",
70720
+ "chatSchedules.allSchedules": "すべての予約を見る",
70721
+ "chatSchedules.advanced": "自動化の詳細設定",
70722
+ "chatSchedules.advancedHint": "AI・実行条件・スクリプト自動化を設定します。AI予約は上の一覧と共通で、スクリプト自動化は別の実行形式です。",
69941
70723
  "chatSchedules.olderReplies": "以前の回答",
69942
70724
  "chatSchedules.newerReplies": "新しい回答",
69943
70725
  "chatSchedules.limitedReplies": "この実行の回答を一部表示しています。全文は会話を開いて確認してください。",
@@ -70383,7 +71165,12 @@ const machineUsageJa = {
70383
71165
  "machineUsage.autoSwitched": "{provider} アカウントを自動切替しました: {from} → {to}",
70384
71166
  "machineUsage.pinned": "固定中",
70385
71167
  "machineUsage.codexAccounts": "Codex アカウント",
70386
- "machineUsage.usageStale": "使用量を更新中"
71168
+ "machineUsage.usageStale": "使用量を更新中",
71169
+ "machineUsage.autoRotation": "アカウント自動切替",
71170
+ "machineUsage.autoRotationOn": "上限に近づくと別のアカウントに自動で切り替えます。",
71171
+ "machineUsage.autoRotationOff": "選択したアカウントを使い続けます。",
71172
+ "machineUsage.autoRotationPaused": "アカウントを選択したため自動切替をオフにしました。",
71173
+ "machineUsage.autoRotationFailed": "自動切替を変更できませんでした。もう一度お試しください。"
70387
71174
  };
70388
71175
  const machineResourceMetricsJa = {
70389
71176
  "machineResourceMetrics.title": "セッションマシンのCPU/メモリ",
@@ -70798,7 +71585,7 @@ const outboundWebhookJa = {
70798
71585
  };
70799
71586
  const messengerChannelsJa = {
70800
71587
  "settings.messengerChannels.title": "メッセンジャーチャネル",
70801
- "settings.messengerChannels.privacyNotice": "接続先メッセンジャーには、リクエストの状態と最終回答のみが送信されます — raw tool output・過去の履歴・添付ファイルは送信されません。メッセンジャー区間はSaycodeのエンドツーエンド暗号化の対象外です。",
71588
+ "settings.messengerChannels.privacyNotice": "接続先メッセンジャーには、リクエストの状態と最終回答が送信されます。Slackでは、その回答でエージェントが指定したプロジェクトフォルダー内のファイルも送信されます。raw tool output・過去の履歴は送信されません。メッセンジャー区間はSaycodeのエンドツーエンド暗号化の対象外です。",
70802
71589
  "settings.messengerChannels.alwaysOnTitle": "アプリ終了後も接続を維持",
70803
71590
  "settings.messengerChannels.alwaysOnHelp": "Saycodeを終了してもメッセージを受信し、作業を続けます。コンピューターがスリープせず、インターネットに接続されている必要があります。",
70804
71591
  "settings.messengerChannels.alwaysOnBlocked": "利用できません: この端末ではボットの資格情報を安全に保存できないため、Desktop終了後は何も受信できません。",
@@ -70956,7 +71743,7 @@ const messengerQuickSetupJa = {
70956
71743
  "settings.messengerChannels.quickSlack.openInSlack": "Slack で開く",
70957
71744
  "settings.messengerChannels.quickSlack.waiting": "Slack で送信するとここに接続リクエストが表示されます。許可すれば完了です。",
70958
71745
  "settings.messengerChannels.quickSlack.pairedWith": "{name} さんの Slack と接続済み",
70959
- "settings.messengerChannels.quickSlack.connectedHelp": "コードを送った DM でボットにそのまま話しかけてください。コマンドはボットをメンションして /help で確認できます。",
71746
+ "settings.messengerChannels.quickSlack.connectedHelp": "コードを送った DM でボットにそのまま話しかけてください。ボットを招待したチャンネルでメンションすると、そのメッセージのスレッドで返信します(チャンネルのメンバーに表示されます)。コマンドはボットをメンションして /help で確認できます。",
70960
71747
  "settings.messengerChannels.quickDiscord.title": "Discord 接続",
70961
71748
  "settings.messengerChannels.quickDiscord.subtitle": "Discord から Saycode にすぐ作業を任せられます。2 ステップで完了します。",
70962
71749
  "settings.messengerChannels.quickDiscord.installTitle": "Discord 機能がまだオフです",
@@ -71261,6 +72048,9 @@ const extensionPanelJa = {
71261
72048
  const extensionInstallJa = {
71262
72049
  "extensions.installOfficial": "拡張機能をインストール",
71263
72050
  "extensions.installNamedOfficial": "{name} をインストール",
72051
+ "extensions.officialUpdate": "{version} に公式アップデート",
72052
+ "extensions.updateSaved": "新しいバージョンはインストールされましたが、仕上げを確認できませんでした。拡張機能の状態を確認してください。",
72053
+ "extensions.projectToolHint": "プロジェクトの会話で、右側の作業パネルのその他(⋮)メニューから開きます: {tools}",
71264
72054
  "extensions.officialName.artifactPublisher": "公開リンク拡張",
71265
72055
  "extensions.officialName.pluginManager": "プラグイン管理拡張",
71266
72056
  "extensions.officialName.projectTemplates": "プロジェクトテンプレート拡張",
@@ -71289,25 +72079,26 @@ const extensionInstallJa = {
71289
72079
  "artifactPublication.error.network": "サーバーに接続できません。ネットワークを確認してください。",
71290
72080
  "artifactPublication.error.unauthenticated": "ログインセッションの有効期限が切れました。再度サインインしてください。",
71291
72081
  "artifactPublication.error.notFound": "ファイルが見つかりません。保存されているか確認してください。",
72082
+ "artifactPublication.error.notFoundInFolder": "作業フォルダー {folder} に {file} が見つかりません。別の worktree で作成したファイルなら、このフォルダーに移してから再試行してください。",
71292
72083
  "artifactPublication.error.tooLarge": "ファイルが大きすぎます。公開する HTML ファイルは 10MB 以下にしてください。",
71293
72084
  "artifactPublication.error.rateLimited": "公開リンクのリクエストが多すぎます。しばらくしてから再試行してください。",
71294
72085
  "artifactPublication.error.rejected": "サーバーがこのファイルの公開リクエストを拒否しました。",
71295
72086
  "artifactPublication.error.serverError": "サーバーエラーが発生しました。しばらくしてから再試行してください。",
71296
72087
  "artifactPublication.error.extensionUnavailable": "拡張機能が無効か、公開権限がありません。設定で Artifact Publisher を確認してください。",
71297
- "artifactPublication.error.invalidResponse": "サーバーの応答を検証できなかったため中止しました。"
72088
+ "artifactPublication.error.invalidResponse": "サーバーの応答を検証できなかったため中止しました。",
72089
+ "extensions.permission.filePreviewsRead": "アプリで開いたファイルのプレビューを読み取ります。元のパスと認証情報は Saycode Core が管理します。"
71298
72090
  };
71299
72091
  const artifactLibraryTranslations = {
71300
72092
  "artifactLibrary.title": "成果物一覧",
71301
72093
  "artifactLibrary.scope": "範囲",
71302
72094
  "artifactLibrary.conversation": "この会話",
71303
72095
  "artifactLibrary.project": "このプロジェクト",
71304
- "artifactLibrary.all": "すべてのプロジェクト",
71305
72096
  "artifactLibrary.search": "ファイル・プロジェクト・会話・パスを検索",
71306
- "artifactLibrary.hint": "会話で作成した文書とプレビューです。元のファイルを開きます。",
71307
72097
  "artifactLibrary.loading": "成果物を検索中…",
71308
72098
  "artifactLibrary.error": "一部の会話を取得できませんでした。再試行してください。",
71309
72099
  "artifactLibrary.empty": "成果物はまだありません。",
71310
72100
  "artifactLibrary.noMatches": "検索結果がありません。",
72101
+ "artifactLibrary.readUnsupported": "このPCでは作業物の元ファイルを開けません。現在はmacOSの作業PCに対応しています。",
71311
72102
  "artifactLibrary.retry": "再試行"
71312
72103
  };
71313
72104
  const personalChatsLandingJa = {
@@ -71847,7 +72638,39 @@ const projectScopeJa = {
71847
72638
  "projectScope.grantRevoked": "ファイルアクセスを取り消してセッションを再起動しました。",
71848
72639
  "projectScope.daemonUpgradeRequired": "Aplus CLI を更新してから、もう一度お試しください。"
71849
72640
  };
72641
+ const machineSettingsLabelsJa = {
72642
+ "machineSettings.listRefreshed": "マシン一覧を更新しました。",
72643
+ "machineSettings.codeGenerated": "登録コードを作成しました。",
72644
+ "machineSettings.nameSaved": "マシン名を保存しました。",
72645
+ "machineSettings.nameCleared": "マシン名を消去しました。",
72646
+ "machineSettings.nameSaveFailed": "マシン名を保存できませんでした。",
72647
+ "machineSettings.removed": "{name}を削除しました。",
72648
+ "machineSettings.zrokStopped": "zrok共有を停止しました。",
72649
+ "machineSettings.zrokStart": "zrokを開始",
72650
+ "machineSettings.zrokStarting": "zrokを開始中",
72651
+ "machineSettings.zrokStop": "zrokを停止",
72652
+ "machineSettings.externalServerUrl": "外部サーバーURL",
72653
+ "machineSettings.externalServerUrlPlaceholder": "{url}のzrok URL",
72654
+ "machineSettings.liveModeOnly": "マシンの登録と削除はライブモードでのみ利用できます。",
72655
+ "machineSettings.issuedCodes": "発行したコード",
72656
+ "machineSettings.generateCode": "登録コードを作成",
72657
+ "machineSettings.registrationCode": "登録コード",
72658
+ "machineSettings.expiresAt": "{time}に期限切れ",
72659
+ "machineSettings.expired": "期限切れ",
72660
+ "machineSettings.copyCommand": "コマンドをコピー",
72661
+ "machineSettings.commandCopied": "コピーしました",
72662
+ "machineSettings.loadingCodes": "コードを読み込み中…",
72663
+ "machineSettings.loadingMachines": "マシンを読み込み中…",
72664
+ "machineSettings.emptyTitle": "マシンがありません",
72665
+ "machineSettings.emptyOrganization": "選択した組織で使えるマシンがありません。",
72666
+ "machineSettings.emptyWorkspace": "現在のワークスペースで使えるマシンがありません。",
72667
+ "machineSettings.rename": "名前を変更",
72668
+ "machineSettings.delete": "削除",
72669
+ "machineSettings.confirmDelete": "削除を確認",
72670
+ "machineSettings.organization": "組織"
72671
+ };
71850
72672
  const machineSettingsConsoleJa = {
72673
+ ...machineSettingsLabelsJa,
71851
72674
  "machineConsole.showDetails": "マシンの詳細を表示",
71852
72675
  "machineConsole.showDetailsHint": "マシン選択でカーソルを合わせるかキーボードで移動すると詳細を表示します。",
71853
72676
  "machineConsole.environmentStatus": "環境・状態",
@@ -72139,9 +72962,15 @@ const sidebarUpdateJa = {
72139
72962
  "sidebarUpdate.completedBody": "Saycode v{version} に更新しました。",
72140
72963
  "sidebarUpdate.installing": "更新を適用中…",
72141
72964
  "sidebarUpdate.update": "アップデート",
72142
- "sidebarUpdate.restartNotice": "再起動の前に会話の状態を保存します。",
72965
+ "sidebarUpdate.restartNotice": "再起動の前に会話の状態を保存します。インストールが終わると Saycode が自動で開きます。",
72143
72966
  "sidebarUpdate.minimize": "最小化",
72144
- "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": "終了"
72145
72974
  };
72146
72975
  const globalPauseJa = {
72147
72976
  "settings.globalPause.enable": "グローバル一時停止(新しい自動アクションを遮断)",
@@ -73466,7 +74295,6 @@ const onboardingJa = {
73466
74295
  "onboarding.page.addProject": "最初のプロジェクトを追加",
73467
74296
  "onboarding.page.configured": "設定済み",
73468
74297
  "onboarding.page.optional": "任意",
73469
- "onboarding.page.computerReveal": "内部テスト: コンピューター・ブラウザー操作を表示",
73470
74298
  "onboarding.page.testNotification": "テスト通知を送信",
73471
74299
  "onboarding.page.testNotificationSending": "テスト通知を送信中…",
73472
74300
  "onboarding.page.testNotificationSuccess": "テスト通知を送信しました。",
@@ -73641,6 +74469,7 @@ const ja = {
73641
74469
  "aiModelPolicy.machineChanged": "選択したPCに接続できないため開始を中断しました。PCを確認して再送してください。",
73642
74470
  "aiModelPolicy.policyChanged": "許可モデルのポリシーが変わったため開始を中断しました。質問と添付を確認して再送してください。",
73643
74471
  "aiModelPolicy.noModels": "このコンピューターで選択できる許可済みモデルがありません。",
74472
+ ...personalAiJa,
73644
74473
  ...conversationTabsJa,
73645
74474
  ...questionComposerJa,
73646
74475
  ...aiRecoveryJa,
@@ -75522,6 +76351,7 @@ const ja = {
75522
76351
  "runtimeEvent.claudeNotFound": "Claude Codeを起動できませんでした。このマシンで実行ファイルが見つかりません。このマシンにhappy-cliを再インストールしてください。",
75523
76352
  "runtimeEvent.claudeStoppedBySignal": "Claude Codeを起動できませんでした。予期せず停止しました({signal})。このマシンのdaemonログを確認してください。",
75524
76353
  "runtimeEvent.claudeCouldNotStart": "このマシンでClaude Codeを起動できませんでした。詳しくはこのマシンのdaemonログを確認してください。",
76354
+ "runtimeEvent.claudeNotSignedIn": "このマシンではClaude Codeにログインしていません。このマシンのdaemonを実行しているユーザーでログインしてから(`claude`を実行して/login)、メッセージをもう一度送信してください。",
75525
76355
  "runtimeEvent.compactionCompleted": "会話の要約が完了しました。これ以降は要約をコンテキストとして続けます。",
75526
76356
  "runtimeEvent.codexError": "Codex エラー: {detail}",
75527
76357
  "runtimeEvent.codexNoImages": "Codex に送れる画像がありませんでした。",
@@ -75864,6 +76694,9 @@ const ja = {
75864
76694
  "projectGitView.branchRename": "Branch 名変更",
75865
76695
  "projectGitView.branchDelete": "Branch 削除",
75866
76696
  "terminal.connectTimeout": "ターミナル接続がタイムアウトしました。",
76697
+ "terminal.strictMachineUnsupported": "セキュリティ強化(strict)マシンではまだリモートターミナルを開けません。マシン上のターミナルを直接お使いください。",
76698
+ "terminal.machineControlUnknown": "このマシンの CLI がセキュリティモードを通知しないため、リモートターミナルを開けません。マシンの happy CLI を更新してください。",
76699
+ "terminal.machineKeyUnavailable": "このマシンのキーを開けないため、リモートターミナルを開きませんでした。アカウントキーのロックを解除してから再度お試しください。",
75867
76700
  "terminal.connectFailed": "ターミナルへの接続に失敗しました。",
75868
76701
  "terminal.cwdPrepFailed": "リモートターミナルの作業ディレクトリを準備できませんでした。",
75869
76702
  "terminal.restoredOutputDivider": "前回のセッション出力",
@@ -83183,6 +84016,18 @@ function contributionList(value2, path2, allowedKeys, parse2) {
83183
84016
  return parse2(entry, entryPath);
83184
84017
  });
83185
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
+ }
83186
84031
  function parseMachineCommands(value2, extensionId, apiVersion) {
83187
84032
  if (apiVersion < 3) fail$7("manifest.machineCommands", "requires Extension API version 3");
83188
84033
  const root = record$4(value2, "manifest.machineCommands");
@@ -83375,6 +84220,23 @@ const EXTENSION_PANEL_SURFACE_SIZES = ["compact", "standard", "wide"];
83375
84220
  function isExtensionPanelSurfaceSize(value2) {
83376
84221
  return EXTENSION_PANEL_SURFACE_SIZES.some((size) => size === value2);
83377
84222
  }
84223
+ function parseExtensionProjectTools(value2, apiVersion) {
84224
+ if (apiVersion < 3) fail$7("manifest.contributes.projectTools", "requires Extension API version 3");
84225
+ return contributionList(value2, "manifest.contributes.projectTools", ["id", "title", "command"], (entry, path2) => ({
84226
+ id: id$1(entry.id, `${path2}.id`),
84227
+ title: string(entry.title, `${path2}.title`),
84228
+ command: id$1(entry.command, `${path2}.command`)
84229
+ }));
84230
+ }
84231
+ function assertExtensionProjectToolTargets(extensionId, tools, commands) {
84232
+ for (const [index, tool] of tools.entries()) {
84233
+ const path2 = `manifest.contributes.projectTools[${index}]`;
84234
+ if (!tool.id.startsWith(`${extensionId}.`)) fail$7(`${path2}.id`, "expected an id declared by the same extension");
84235
+ const command = tool.command.startsWith(`${extensionId}.`) ? commands.find((candidate) => candidate.id === tool.command) : void 0;
84236
+ if (!command) fail$7(`${path2}.command`, "expected a command declared by the same extension");
84237
+ if (!command.panelId) fail$7(`${path2}.command`, "expected a command that opens a panel");
84238
+ }
84239
+ }
83378
84240
  const EXTENSION_ARTIFACT_ACTION_OPERATIONS = ["publishPublic"];
83379
84241
  const EXTENSION_ARTIFACT_SOURCE_TYPES = ["project-file", "personal-chat-file"];
83380
84242
  const ID_PATTERN$2 = /^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
@@ -83846,6 +84708,7 @@ const EXTENSION_PERMISSIONS = [
83846
84708
  "browserViewer.install",
83847
84709
  "browserViewer.installChrome",
83848
84710
  "artifacts.publishPublic",
84711
+ "filePreviews.read",
83849
84712
  // One permission per channel wire method (`src/domain/channelProtocol.ts`), deliberately not a
83850
84713
  // single adapter grant. Receiving and acking messages is transport work; reading a session,
83851
84714
  // driving it, or approving a tool call once are three larger and separately-refusable
@@ -83860,10 +84723,10 @@ const EXTENSION_PERMISSIONS = [
83860
84723
  "sessions.control",
83861
84724
  "sessions.approveOnce"
83862
84725
  ];
83863
- const SAYCODE_EXTENSION_API_VERSION = 3;
84726
+ const SAYCODE_EXTENSION_API_VERSION = 4;
83864
84727
  const SAYCODE_EXTENSION_MINIMUM_API_VERSION = 2;
83865
84728
  const MAX_EXTENSION_API_WINDOW_WIDTH = 2;
83866
- const SAYCODE_EXTENSION_ENGINE_VERSION = "1.1.0";
84729
+ const SAYCODE_EXTENSION_ENGINE_VERSION = "1.2.0";
83867
84730
  const SAYCODE_CHANNEL_API_VERSION = CHANNEL_API_VERSION;
83868
84731
  const SAYCODE_CHANNEL_MINIMUM_API_VERSION = CHANNEL_MINIMUM_API_VERSION;
83869
84732
  class UnsupportedChannelApiVersionError2 extends Error {
@@ -83886,7 +84749,7 @@ const SEMVER_PATTERN$1 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)
83886
84749
  const ENGINE_RANGE_PATTERN = /^(?:\^|~|>=|<=|>|<)?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
83887
84750
  const ENTRYPOINT_PATTERN = /\.(?:cjs|mjs|js)$/;
83888
84751
  const LOCALE_PATTERN = /^[a-z]{2,3}(?:-[A-Z]{2})?$/;
83889
- const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["commands", "settings", "panels", "projectTemplates", "machineActions", "artifactActions", "channels"]);
84752
+ const CONTRIBUTION_KEYS = /* @__PURE__ */ new Set(["commands", "settings", "panels", "projectTemplates", "machineActions", "projectTools", "artifactActions", "channels", "fileViewers"]);
83890
84753
  const V2_PERMISSIONS = /* @__PURE__ */ new Set([
83891
84754
  "browserViewer.open",
83892
84755
  "browserViewer.install",
@@ -83947,7 +84810,7 @@ function engineSupports(range, version2) {
83947
84810
  function parseApiVersion(value2, options) {
83948
84811
  const current2 = options.supportedApiVersion;
83949
84812
  const floor = options.minimumSupportedApiVersion ?? current2;
83950
- 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)) {
83951
84814
  fail$7(
83952
84815
  "manifest.apiVersion",
83953
84816
  `invalid Desktop API support window [${floor}, ${current2}]`
@@ -83988,6 +84851,7 @@ function parseContributions(value2, apiVersion, channelApiVersion) {
83988
84851
  fail$7("manifest.contributes.artifactActions", "requires Extension API version 3");
83989
84852
  }
83990
84853
  const result2 = {};
84854
+ if (parsed2.fileViewers !== void 0) result2.fileViewers = parseExtensionFileViewers(parsed2.fileViewers, apiVersion);
83991
84855
  if (parsed2.commands !== void 0) {
83992
84856
  result2.commands = contributionList(
83993
84857
  parsed2.commands,
@@ -84093,6 +84957,7 @@ function parseContributions(value2, apiVersion, channelApiVersion) {
84093
84957
  }
84094
84958
  );
84095
84959
  }
84960
+ if (parsed2.projectTools !== void 0) result2.projectTools = parseExtensionProjectTools(parsed2.projectTools, apiVersion);
84096
84961
  if (parsed2.artifactActions !== void 0) {
84097
84962
  result2.artifactActions = parseExtensionArtifactActions(parsed2.artifactActions);
84098
84963
  }
@@ -84105,6 +84970,7 @@ function parseContributions(value2, apiVersion, channelApiVersion) {
84105
84970
  ...result2.panels ?? [],
84106
84971
  ...result2.projectTemplates ?? [],
84107
84972
  ...result2.machineActions ?? [],
84973
+ ...result2.projectTools ?? [],
84108
84974
  ...result2.artifactActions ?? [],
84109
84975
  ...result2.channels ?? []
84110
84976
  ].map((entry) => entry.id);
@@ -84137,6 +85003,7 @@ function parseExtensionManifest(value2, options) {
84137
85003
  fail$7("manifest.permissions", `unknown permission ${parsed2}`);
84138
85004
  }
84139
85005
  const typed = parsed2;
85006
+ if (typed === "filePreviews.read" && apiVersion < 4) fail$7("manifest.permissions", "filePreviews.read requires Extension API version 4");
84140
85007
  if (apiVersion < 2 && V2_PERMISSIONS.has(typed)) {
84141
85008
  fail$7(`manifest.permissions[${index}]`, "requires Extension API version 2");
84142
85009
  }
@@ -84208,6 +85075,8 @@ function parseExtensionManifest(value2, options) {
84208
85075
  );
84209
85076
  }
84210
85077
  }
85078
+ assertExtensionFileViewers(parsedId, contributes.fileViewers ?? [], contributes.panels ?? [], permissions);
85079
+ assertExtensionProjectToolTargets(parsedId, contributes.projectTools ?? [], contributes.commands ?? []);
84211
85080
  return {
84212
85081
  id: parsedId,
84213
85082
  version: version2,
@@ -84265,15 +85134,7 @@ class OfficialExtensionInstaller {
84265
85134
  * ones are the rest of the panel.
84266
85135
  */
84267
85136
  async installable() {
84268
- let bundled = [];
84269
- try {
84270
- bundled = (await this.readCatalog()).extensions;
84271
- } catch {
84272
- bundled = [];
84273
- }
84274
- const versions = /* @__PURE__ */ new Map();
84275
- for (const entry of bundled) versions.set(entry.id, entry.version);
84276
- for (const entry of this.options.remote?.descriptors() ?? []) versions.set(entry.id, entry.version);
85137
+ const versions = await this.offeredVersions();
84277
85138
  return [...versions].filter(([id2, version2]) => !this.options.registry.get(id2) && !this.options.revocations?.isRevoked(id2, version2)).map(([id2, version2]) => ({ id: id2, version: version2 }));
84278
85139
  }
84279
85140
  async install(id2) {
@@ -84292,6 +85153,48 @@ class OfficialExtensionInstaller {
84292
85153
  }
84293
85154
  return { id: installed.id, version: installed.version, releaseUrl: source2.metadata.releaseUrl };
84294
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
+ }
84295
85198
  async reconcileBundledCompatibility() {
84296
85199
  const installedEntries = this.options.registry.entries();
84297
85200
  if (installedEntries.length === 0) return;
@@ -84349,6 +85252,26 @@ class OfficialExtensionInstaller {
84349
85252
  if (!metadata) throw new Error(`official extension is unavailable: ${id2}`);
84350
85253
  return { archive: await this.readArchive(metadata), metadata };
84351
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
+ }
84352
85275
  async readCatalog() {
84353
85276
  try {
84354
85277
  return catalog(JSON.parse(await readFile(join(this.options.resourceRoot, "catalog.v1.json"), "utf8")));
@@ -88322,6 +89245,107 @@ class ChannelAuthCustody {
88322
89245
  onWindowClosed(_windowId) {
88323
89246
  }
88324
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
+ }
88325
89349
  class ChannelCapabilityError extends Error {
88326
89350
  constructor(code, message) {
88327
89351
  super(`${code}: ${message}`);
@@ -88912,6 +89936,25 @@ function channelReconnectDelayMs(attempt2, options) {
88912
89936
  const exponential = Math.min(options.maxMs, options.baseMs * 2 ** Math.max(0, attempt2 - 1));
88913
89937
  return Math.floor(exponential * (0.5 + 0.5 * options.jitter()));
88914
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
+ });
88915
89958
  const MAX_SELECTOR_DEPTH = 6;
88916
89959
  const MAX_EVENTS_PER_PAYLOAD = 100;
88917
89960
  const TELEGRAM = Object.freeze({
@@ -89083,7 +90126,9 @@ const SLACK$1 = Object.freeze({
89083
90126
  credentialPlacement: "authorizationBearer",
89084
90127
  parameterPlacement: "jsonBody",
89085
90128
  allowedParams: Object.freeze(["channel", "ts", "text"])
89086
- }
90129
+ },
90130
+ // Files attached to a final answer (specs/channel-file-replies). Core-only, like the two above.
90131
+ ...SLACK_FILE_UPLOAD_OPERATIONS
89087
90132
  }),
89088
90133
  inbound: Object.freeze({
89089
90134
  eventsPath: Object.freeze(["events"]),
@@ -89241,64 +90286,7 @@ function channelProviderContract(provider2) {
89241
90286
  if (!contract) throw new Error(`no channel contract for provider ${provider2}`);
89242
90287
  return contract;
89243
90288
  }
89244
- async function withdrawIntakeDurably(cancelled, connectionKeys, deps) {
89245
- const undurable = await journalCancelledIntake(cancelled, deps);
89246
- if (undurable.length === 0) return EMPTY_WITHDRAWAL;
89247
- const unsafeToReopen = [];
89248
- for (const key of connectionKeys) {
89249
- try {
89250
- await deps.revokeGeneration(key);
89251
- } catch {
89252
- unsafeToReopen.push(key);
89253
- deps.onDiagnostic?.({
89254
- code: "INTAKE_WITHDRAWAL_NOT_ENFORCEABLE",
89255
- message: "a withdrawal was neither recorded nor fenced by a generation move"
89256
- });
89257
- }
89258
- }
89259
- return Object.freeze({ unsafeToReopen: Object.freeze(unsafeToReopen) });
89260
- }
89261
- const EMPTY_WITHDRAWAL = Object.freeze({
89262
- unsafeToReopen: Object.freeze([])
89263
- });
89264
- async function journalCancelledIntake(entries, deps) {
89265
- const undurable = [];
89266
- for (const entry of entries) {
89267
- try {
89268
- await deps.journal.record(entry);
89269
- } catch {
89270
- undurable.push(entry.id);
89271
- deps.onDiagnostic?.({
89272
- code: "INTAKE_CANCELLATION_NOT_DURABLE",
89273
- message: "a cancelled request could not be recorded"
89274
- });
89275
- }
89276
- }
89277
- return Object.freeze(undurable);
89278
- }
89279
- function intakeStateForTurnOutcome(outcome) {
89280
- if (outcome.kind === "final") return "executed";
89281
- if (outcome.status === "cancelled") return "cancelled";
89282
- if (outcome.status === "no-final-answer") return "executed";
89283
- return "failed";
89284
- }
89285
- async function settleIntakeEntry(queue, requestId, state, deps) {
89286
- let settled;
89287
- try {
89288
- settled = queue.transition(requestId, state);
89289
- } catch {
89290
- return;
89291
- }
89292
- try {
89293
- await deps.journal.record(settled);
89294
- } catch {
89295
- deps.onDiagnostic?.({
89296
- code: "INTAKE_TERMINAL_NOT_DURABLE",
89297
- message: "a finished request could not be recorded"
89298
- });
89299
- }
89300
- }
89301
- function readString$1(source2, path2) {
90289
+ function readString$2(source2, path2) {
89302
90290
  let current2 = source2;
89303
90291
  for (const key of path2) {
89304
90292
  if (!current2 || typeof current2 !== "object" || Array.isArray(current2)) return null;
@@ -89333,12 +90321,14 @@ function withText(event, text2) {
89333
90321
  return text2 === event.text ? event : { ...event, text: text2 };
89334
90322
  }
89335
90323
  function refineSlack(entry, event, context) {
89336
- const channelType = readString$1(entry, ["payload", "event", "channel_type"]);
90324
+ const channelType = readString$2(entry, ["payload", "event", "channel_type"]);
89337
90325
  const isDirect = channelType === "im" || channelType === "mpim";
89338
90326
  const rootable = !event.action && !isDirect;
89339
- 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);
89340
90328
  const text2 = canonicalizeVerifiedMention(event.text, context.verifiedBotRef);
89341
- 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;
89342
90332
  }
89343
90333
  function refineDiscord(event, context) {
89344
90334
  const text2 = canonicalizeVerifiedMention(event.text, context.verifiedBotRef);
@@ -89491,7 +90481,7 @@ function readRecord(source2, key) {
89491
90481
  const value2 = source2[key];
89492
90482
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
89493
90483
  }
89494
- function readString(source2, key) {
90484
+ function readString$1(source2, key) {
89495
90485
  const value2 = source2?.[key];
89496
90486
  return typeof value2 === "string" && value2.length > 0 ? value2 : null;
89497
90487
  }
@@ -89519,7 +90509,7 @@ function normalizeDiscordInteraction(frame) {
89519
90509
  if (!d || d.type !== DISCORD_APPLICATION_COMMAND) return null;
89520
90510
  const data = readRecord(d, "data");
89521
90511
  if (!data || data.type !== DISCORD_CHAT_INPUT_DATA) return null;
89522
- const name = readString(data, "name");
90512
+ const name = readString$1(data, "name");
89523
90513
  if (!name || !Object.prototype.hasOwnProperty.call(CHANNEL_COMMAND_SCHEMA, name)) return null;
89524
90514
  const specs = CHANNEL_COMMAND_SCHEMA[name];
89525
90515
  const declared = new Set(specs.map((spec) => spec.endsWith("!") ? spec.slice(0, -1) : spec));
@@ -89529,7 +90519,7 @@ function normalizeDiscordInteraction(frame) {
89529
90519
  if (!Array.isArray(options)) return null;
89530
90520
  for (const option of options) {
89531
90521
  const entry = option && typeof option === "object" && !Array.isArray(option) ? option : null;
89532
- const optionName = readString(entry, "name");
90522
+ const optionName = readString$1(entry, "name");
89533
90523
  const value2 = entry?.value;
89534
90524
  if (!optionName || !declared.has(optionName)) return null;
89535
90525
  if (entry?.type !== DISCORD_STRING_OPTION$1 || typeof value2 !== "string") return null;
@@ -89543,9 +90533,9 @@ function normalizeDiscordInteraction(frame) {
89543
90533
  }
89544
90534
  const direct = readRecord(d, "user");
89545
90535
  const membership = readRecord(readRecord(d, "member"), "user");
89546
- 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;
89547
90537
  const user = membership ?? direct;
89548
- const senderRef = readString(user, "id");
90538
+ const senderRef = readString$1(user, "id");
89549
90539
  if (!senderRef) return null;
89550
90540
  const bot = user?.bot;
89551
90541
  if (bot !== void 0 && typeof bot !== "boolean") return null;
@@ -89555,11 +90545,11 @@ function normalizeDiscordInteraction(frame) {
89555
90545
  entry: {
89556
90546
  ...frame,
89557
90547
  d: {
89558
- id: readString(d, "id"),
89559
- channel_id: readString(d, "channel_id"),
90548
+ id: readString$1(d, "id"),
90549
+ channel_id: readString$1(d, "channel_id"),
89560
90550
  author: {
89561
90551
  id: senderRef,
89562
- ...readString(user, "username") ? { username: readString(user, "username") } : {},
90552
+ ...readString$1(user, "username") ? { username: readString$1(user, "username") } : {},
89563
90553
  ...bot === void 0 ? {} : { bot }
89564
90554
  },
89565
90555
  content: text2
@@ -89591,21 +90581,21 @@ function normalizeSlackAction(frame) {
89591
90581
  if (!pressed || pressed.type !== "button") return null;
89592
90582
  const action = parseChannelApprovalActionId(pressed.action_id);
89593
90583
  if (!action) return null;
89594
- const senderRef = readString(readRecord(payload, "user"), "id");
90584
+ const senderRef = readString$1(readRecord(payload, "user"), "id");
89595
90585
  const container = readRecord(payload, "container");
89596
90586
  if (!container || container.type !== "message") return null;
89597
- const conversationRef = readString(container, "channel_id");
89598
- const stated = readString(readRecord(payload, "channel"), "id");
90587
+ const conversationRef = readString$1(container, "channel_id");
90588
+ const stated = readString$1(readRecord(payload, "channel"), "id");
89599
90589
  if (!conversationRef || stated !== null && stated !== conversationRef) return null;
89600
- const threadRef = readString(readRecord(payload, "message"), "thread_ts") ?? readString(container, "thread_ts");
89601
- 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");
89602
90592
  if (!senderRef || !pressedAt) return null;
89603
90593
  return {
89604
90594
  entry: {
89605
90595
  ...frame,
89606
90596
  payload: {
89607
90597
  ...payload,
89608
- team_id: readString(readRecord(payload, "team"), "id") ?? void 0,
90598
+ team_id: readString$1(readRecord(payload, "team"), "id") ?? void 0,
89609
90599
  // Rebuilt, not copied: `payload.message` describes the bot's own prompt, and carrying its
89610
90600
  // `user`/`bot_id` through would attribute the click to Saycode.
89611
90601
  event: {
@@ -89629,10 +90619,10 @@ function normalizeDiscordAction(frame, d) {
89629
90619
  if (!action) return null;
89630
90620
  const direct = readRecord(d, "user");
89631
90621
  const membership = readRecord(readRecord(d, "member"), "user");
89632
- 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;
89633
90623
  const user = membership ?? direct;
89634
- const senderRef = readString(user, "id");
89635
- const conversationRef = readString(d, "channel_id");
90624
+ const senderRef = readString$1(user, "id");
90625
+ const conversationRef = readString$1(d, "channel_id");
89636
90626
  if (!senderRef || !conversationRef) return null;
89637
90627
  const bot = user?.bot;
89638
90628
  if (bot !== void 0 && typeof bot !== "boolean") return null;
@@ -89641,7 +90631,7 @@ function normalizeDiscordAction(frame, d) {
89641
90631
  ...frame,
89642
90632
  d: {
89643
90633
  // The interaction id, unique per press — a second click is its own event.
89644
- id: readString(d, "id"),
90634
+ id: readString$1(d, "id"),
89645
90635
  channel_id: conversationRef,
89646
90636
  author: { id: senderRef, ...bot === void 0 ? {} : { bot } },
89647
90637
  content: action.decision
@@ -90549,6 +91539,344 @@ function classifyProgressResult(contract, result2, onDiagnostic) {
90549
91539
  }
90550
91540
  return { ok: false, retryable };
90551
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
+ }
90552
91880
  const PROGRESS_MIN_EDIT_INTERVAL_MS = 1500;
90553
91881
  const PROGRESS_HEARTBEAT_MS = 6e4;
90554
91882
  const PROGRESS_TERMINAL_ATTEMPTS = 3;
@@ -90951,64 +92279,6 @@ function frozenRequestBinding(binding) {
90951
92279
  destination: Object.freeze({ ...binding.destination })
90952
92280
  });
90953
92281
  }
90954
- async function sendChannelOutbound(message, deps) {
90955
- const contract = channelProviderContract(deps.provider);
90956
- const mapping = contract.outbound;
90957
- if (message.body.length > mapping.maxTextLength) {
90958
- deps.onDiagnostic?.({
90959
- code: "REPLY_TOO_LONG",
90960
- message: `the reply exceeds ${mapping.maxTextLength} characters for this provider`
90961
- });
90962
- return "failed";
90963
- }
90964
- if (!deps.isCurrent()) return "failed";
90965
- const params = {
90966
- [mapping.conversationParam]: message.destination.conversationRef,
90967
- [mapping.textParam]: message.body
90968
- };
90969
- if (mapping.threadParam && message.destination.threadRef) {
90970
- params[mapping.threadParam] = message.destination.threadRef;
90971
- }
90972
- if (mapping.replyToParam && message.destination.replyToTransportEventId) {
90973
- params[mapping.replyToParam] = mapping.replyToIsStructured && mapping.replyToStructuredField ? { [mapping.replyToStructuredField]: message.destination.replyToTransportEventId } : message.destination.replyToTransportEventId;
90974
- }
90975
- if (deps.interactive && mapping.interactiveParam) {
90976
- params[mapping.interactiveParam] = deps.interactive;
90977
- }
90978
- let result2;
90979
- try {
90980
- result2 = await deps.executor.execute(message.connectionId, {
90981
- operation: mapping.operation,
90982
- params,
90983
- credentialRevision: message.connectionRevision
90984
- }, void 0, () => {
90985
- if (!deps.stillSending()) throw new Error("withdrawn");
90986
- });
90987
- } catch (error) {
90988
- if (error?.code === "SEND_FENCED") {
90989
- deps.onDiagnostic?.({
90990
- code: "REPLY_WITHDRAWN",
90991
- message: "the reply was withdrawn before it was sent"
90992
- });
90993
- return "failed";
90994
- }
90995
- deps.onDiagnostic?.({ code: "REPLY_SEND_UNCONFIRMED", message: "the reply could not be confirmed" });
90996
- return "unknown";
90997
- }
90998
- const refusal = pollRefusal(contract, result2);
90999
- if (refusal.refused) {
91000
- deps.onDiagnostic?.({
91001
- code: "REPLY_REFUSED",
91002
- message: `the provider refused the reply (${refusal.reason})`
91003
- });
91004
- return "failed";
91005
- }
91006
- if (deps.onDelivered) {
91007
- const messageId2 = readProviderMessageId(deps.provider, result2.body);
91008
- if (messageId2) deps.onDelivered(messageId2);
91009
- }
91010
- return "sent";
91011
- }
91012
92282
  function fail(detail) {
91013
92283
  throw new Error(`extension wire message must be JSON-serializable: ${detail}`);
91014
92284
  }
@@ -91448,9 +92718,9 @@ class ChannelReplyRuntime {
91448
92718
  this.bindings.delete(outcome.requestId);
91449
92719
  } else if (launchesContinuation) this.backgroundContinuations.set(outcome.requestId, 1);
91450
92720
  else this.bindings.delete(outcome.requestId);
91451
- const body = outcome.kind === "final" ? outcome.text : channelTurnFailureText(outcome.status);
91452
92721
  const replyBinding = continuationIndex === void 0 ? binding : { ...binding, requestId: `${binding.requestId}:background:${continuationIndex}` };
91453
- 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);
91454
92724
  this.drain(binding.sessionId);
91455
92725
  return outcome;
91456
92726
  }
@@ -91543,7 +92813,7 @@ class ChannelReplyRuntime {
91543
92813
  code: "REPLY_NOT_ADMITTED",
91544
92814
  message: `the reply was not admitted (${admission.reason})`
91545
92815
  });
91546
- return;
92816
+ return false;
91547
92817
  }
91548
92818
  try {
91549
92819
  await this.options.journal.recordOutbound(admission.message);
@@ -91553,9 +92823,10 @@ class ChannelReplyRuntime {
91553
92823
  code: "REPLY_NOT_DURABLE",
91554
92824
  message: "the reply could not be recorded"
91555
92825
  });
91556
- return;
92826
+ return false;
91557
92827
  }
91558
92828
  this.options.outbox.commit(admission.message);
92829
+ return true;
91559
92830
  }
91560
92831
  /** Serial per session; a second call while one is draining joins rather than overlapping. */
91561
92832
  drain(sessionId) {
@@ -91586,6 +92857,7 @@ class ChannelReplyRuntime {
91586
92857
  const settled = this.options.outbox.transition(message.id, outcome);
91587
92858
  await this.options.journal.recordOutbound(settled);
91588
92859
  await this.options.journal.recordOutboundTombstone(tombstoneOf(settled));
92860
+ this.options.onReplySettled?.(settled);
91589
92861
  }
91590
92862
  }
91591
92863
  /** Both halves of "may this reply still leave": the live composition *and* a live adapter. */
@@ -91811,11 +93083,22 @@ class ChannelConnectionComposition {
91811
93083
  mintHandle: this.options.mintId
91812
93084
  }),
91813
93085
  progress,
93086
+ filesAttachable: this.options.sessionFiles !== void 0,
91814
93087
  reply: new ChannelReplyRuntime({
91815
93088
  // Display only, and deliberately after every delivery decision: the rail is handed the
91816
93089
  // provider's id for an acknowledgement that has already been delivered, so nothing here
91817
93090
  // can affect whether a reply goes out or how it is recorded.
91818
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
+ }),
91819
93102
  ...this.options.formatReply ? { formatReply: this.options.formatReply } : {},
91820
93103
  provider: input.provider,
91821
93104
  connectionId: input.identity.connectionId,
@@ -91982,6 +93265,39 @@ function bindChannelControlRequest(input, deps) {
91982
93265
  return { ok: false, code: typeof code === "string" ? code : "REQUEST_REFUSED" };
91983
93266
  }
91984
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
+ }
91985
93301
  const SYNCHRONOUS_OPERATIONS = new Set(
91986
93302
  CHANNEL_SESSION_OPERATIONS.filter((operation) => operation !== "prompt")
91987
93303
  );
@@ -92002,6 +93318,7 @@ async function dispatchChannelControl(input, deps) {
92002
93318
  });
92003
93319
  return !!authCustody.current(parts2.principalKey) && !!current2 && current2.revision === input.event.connectionRevision && generations.current(connectionKey) === input.event.connectionRevision;
92004
93320
  };
93321
+ await adoptInvitedMention(input, { parts: parts2, grants, answerable, now: deps.now });
92005
93322
  const word = classifyChannelCommandWord(input.event.text ?? "");
92006
93323
  if (word) {
92007
93324
  if (answerable()) {
@@ -95996,7 +97313,7 @@ function createChannelRequestCoordinator(deps) {
95996
97313
  generations,
95997
97314
  grants,
95998
97315
  custody: authCustody,
95999
- operations,
97316
+ operations: withChannelFileReplyHint(parts2, operations),
96000
97317
  isEstopEngaged: () => options.isEstopEngaged(),
96001
97318
  ...options.mayActForConnection ? { mayActForConnection: options.mayActForConnection } : {},
96002
97319
  ownership,
@@ -98187,6 +99504,11 @@ async function assemble(options, rootDir) {
98187
99504
  now: () => Date.now(),
98188
99505
  mintId: () => randomUUID(),
98189
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
+ } } : {},
98190
99512
  approvalOpen: (approvalHandle) => approvalOpen?.(approvalHandle) ?? false,
98191
99513
  onDiagnostic: (detail) => options.onDiagnostic?.(detail),
98192
99514
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},