@buzzni/saycode-cli 0.3.2 → 0.4.0
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/README.md +9 -2
- package/index.mjs +1897 -225
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -40,9 +40,9 @@ function _mergeNamespaces(n, m) {
|
|
|
40
40
|
}
|
|
41
41
|
return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" }));
|
|
42
42
|
}
|
|
43
|
-
const version = "0.
|
|
43
|
+
const version$1 = "0.4.0";
|
|
44
44
|
const manifest = {
|
|
45
|
-
version
|
|
45
|
+
version: version$1
|
|
46
46
|
};
|
|
47
47
|
const AGENT_CONTROL_VERBS = ["steer", "stop"];
|
|
48
48
|
function parseAgentControlArgs(verb, target, rest, hasPositionalTarget) {
|
|
@@ -58,13 +58,13 @@ function parseAgentControlArgs(verb, target, rest, hasPositionalTarget) {
|
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
const AGENT_VERBS = ["whoami", "ls", "read", "wait", "prompt", "spawn", ...AGENT_CONTROL_VERBS];
|
|
61
|
-
function fail(message) {
|
|
61
|
+
function fail$1(message) {
|
|
62
62
|
return { command: "error", message };
|
|
63
63
|
}
|
|
64
64
|
function parseAgentArgs(flags, positionals) {
|
|
65
65
|
const [verb, ...rest] = positionals;
|
|
66
66
|
if (!verb || !AGENT_VERBS.includes(verb)) {
|
|
67
|
-
return fail(`agent needs one of: ${AGENT_VERBS.join(", ")}`);
|
|
67
|
+
return fail$1(`agent needs one of: ${AGENT_VERBS.join(", ")}`);
|
|
68
68
|
}
|
|
69
69
|
const timeout = flags.timeoutMs !== void 0 ? { timeoutMs: flags.timeoutMs } : {};
|
|
70
70
|
const hasPositionalTarget = flags.sessionId === void 0 && rest[0] !== void 0;
|
|
@@ -75,20 +75,31 @@ function parseAgentArgs(flags, positionals) {
|
|
|
75
75
|
case "whoami":
|
|
76
76
|
return { command: "agent", verb: "whoami" };
|
|
77
77
|
case "ls":
|
|
78
|
-
return { command: "agent", verb: "ls" };
|
|
78
|
+
return { command: "agent", verb: "ls", ...flags.status ? { status: true } : {} };
|
|
79
79
|
case "read": {
|
|
80
|
-
if (!target) return fail("agent read needs a session id (see `saycode agent ls`)");
|
|
81
|
-
return {
|
|
80
|
+
if (!target) return fail$1("agent read needs a session id (see `saycode agent ls`)");
|
|
81
|
+
return {
|
|
82
|
+
command: "agent",
|
|
83
|
+
verb: "read",
|
|
84
|
+
sessionId: target,
|
|
85
|
+
...flags.last !== void 0 ? { last: flags.last } : {},
|
|
86
|
+
...flags.since !== void 0 ? { since: flags.since } : {}
|
|
87
|
+
};
|
|
82
88
|
}
|
|
83
89
|
case "wait": {
|
|
84
|
-
if (
|
|
85
|
-
|
|
90
|
+
if (flags.any) {
|
|
91
|
+
if (rest.length < 2) return fail$1("agent wait --any needs two or more session ids");
|
|
92
|
+
if (!flags.until) return fail$1("agent wait needs --until turn-end|waiting-input|completed");
|
|
93
|
+
return { command: "agent", verb: "wait", sessionIds: [...rest], until: flags.until, ...timeout };
|
|
94
|
+
}
|
|
95
|
+
if (!target) return fail$1("agent wait needs a session id (see `saycode agent ls`)");
|
|
96
|
+
if (!flags.until) return fail$1("agent wait needs --until turn-end|waiting-input|completed");
|
|
86
97
|
return { command: "agent", verb: "wait", sessionId: target, until: flags.until, ...timeout };
|
|
87
98
|
}
|
|
88
99
|
case "prompt": {
|
|
89
|
-
if (!target) return fail("agent prompt needs a session id (see `saycode agent ls`)");
|
|
100
|
+
if (!target) return fail$1("agent prompt needs a session id (see `saycode agent ls`)");
|
|
90
101
|
const text2 = rest.slice(hasPositionalTarget ? 1 : 0).join(" ").trim();
|
|
91
|
-
if (!text2) return fail("agent prompt needs the text to send");
|
|
102
|
+
if (!text2) return fail$1("agent prompt needs the text to send");
|
|
92
103
|
return {
|
|
93
104
|
command: "agent",
|
|
94
105
|
verb: "prompt",
|
|
@@ -104,16 +115,16 @@ function parseAgentArgs(flags, positionals) {
|
|
|
104
115
|
const files = flags.files ?? [];
|
|
105
116
|
if (flags.handoff) {
|
|
106
117
|
if (flags.prompt !== void 0) {
|
|
107
|
-
return fail("agent spawn --handoff sends the conversation, so it cannot also take --prompt");
|
|
118
|
+
return fail$1("agent spawn --handoff sends the conversation, so it cannot also take --prompt");
|
|
108
119
|
}
|
|
109
120
|
if (files.length > 0) {
|
|
110
|
-
return fail("agent spawn --handoff sends the conversation, so it cannot also take --file");
|
|
121
|
+
return fail$1("agent spawn --handoff sends the conversation, so it cannot also take --file");
|
|
111
122
|
}
|
|
112
123
|
const mode = flags.mode?.trim() || "summary";
|
|
113
124
|
if (mode === "full") {
|
|
114
|
-
return fail("agent spawn --handoff does not support --mode full yet; only summary is available");
|
|
125
|
+
return fail$1("agent spawn --handoff does not support --mode full yet; only summary is available");
|
|
115
126
|
}
|
|
116
|
-
if (mode !== "summary") return fail(`agent spawn --handoff got an unknown --mode: ${mode}`);
|
|
127
|
+
if (mode !== "summary") return fail$1(`agent spawn --handoff got an unknown --mode: ${mode}`);
|
|
117
128
|
const from = flags.from?.trim();
|
|
118
129
|
return {
|
|
119
130
|
command: "agent",
|
|
@@ -125,7 +136,7 @@ function parseAgentArgs(flags, positionals) {
|
|
|
125
136
|
};
|
|
126
137
|
}
|
|
127
138
|
const text2 = flags.prompt?.trim();
|
|
128
|
-
if (!text2) return fail("agent spawn needs --prompt <text>");
|
|
139
|
+
if (!text2) return fail$1("agent spawn needs --prompt <text>");
|
|
129
140
|
return {
|
|
130
141
|
command: "agent",
|
|
131
142
|
verb: "spawn",
|
|
@@ -137,7 +148,7 @@ function parseAgentArgs(flags, positionals) {
|
|
|
137
148
|
};
|
|
138
149
|
}
|
|
139
150
|
default:
|
|
140
|
-
return fail(`unsupported agent verb: ${verb}`);
|
|
151
|
+
return fail$1(`unsupported agent verb: ${verb}`);
|
|
141
152
|
}
|
|
142
153
|
}
|
|
143
154
|
function fencedMarkdownRanges(content) {
|
|
@@ -316,7 +327,7 @@ const agentBoardKo = {
|
|
|
316
327
|
"agentBoard.columnReviewHint": "카드를 놓으면 리뷰를 요청합니다",
|
|
317
328
|
"agentBoard.columnDone": "완료",
|
|
318
329
|
"agentBoard.columnDoneHint": "카드를 놓으면 대화를 완료로 이동합니다",
|
|
319
|
-
"agentBoard.columnMerged": "PR
|
|
330
|
+
"agentBoard.columnMerged": "PR 병합됨",
|
|
320
331
|
"agentBoard.columnMergedHint": "PR이 머지된 대화",
|
|
321
332
|
"agentBoard.title": "에이전트 보드",
|
|
322
333
|
"agentBoard.description": "실행 중인 모든 에이전트 대화를 한눈에 보고, 카드를 옮겨 다음 작업을 지시하세요.",
|
|
@@ -370,7 +381,7 @@ const agentBoardKo = {
|
|
|
370
381
|
"agentBoard.mergeDialogTitle": "Pull request를 머지할까요?",
|
|
371
382
|
"agentBoard.mergeCopy": "PR #{prNumber}을 squash 방식으로 머지합니다. 이 작업은 GitHub 저장소에 즉시 반영됩니다.",
|
|
372
383
|
"agentBoard.mergeCancel": "취소",
|
|
373
|
-
"agentBoard.mergeConfirm": "
|
|
384
|
+
"agentBoard.mergeConfirm": "병합",
|
|
374
385
|
"agentBoard.cardReopenTitle": "완료를 해제하고 이어서 작업합니다. 완료 시 삭제된 작업 트리는 브랜치 기준으로 복구합니다.",
|
|
375
386
|
"agentBoard.cardReopen": "완료 해제",
|
|
376
387
|
"agentBoard.cardCreatePrTitle": "에이전트에게 commit 및 PR 생성을 요청",
|
|
@@ -399,7 +410,7 @@ const agentBoardKo = {
|
|
|
399
410
|
"agentBoard.findSessionError": "대화를 찾을 수 없습니다. 새로고침 후 다시 시도해주세요.",
|
|
400
411
|
"agentBoard.autopilot.enable": "오토파일럿",
|
|
401
412
|
"agentBoard.autopilot.unavailableWorktree": "오토파일럿 사용 불가 · worktree 필요",
|
|
402
|
-
"agentBoard.autopilot.enableTitle": "작업이 완료되면
|
|
413
|
+
"agentBoard.autopilot.enableTitle": "작업이 완료되면 현재 에이전트에 셀프 리뷰를 시키고, medium 이상 지적이 없으면 Commit & PR까지 자동 진행합니다.",
|
|
403
414
|
"agentBoard.autopilot.waiting": "오토파일럿 · 턴 종료 대기",
|
|
404
415
|
"agentBoard.autopilot.reviewRequested": "오토파일럿 · 리뷰 시작 중",
|
|
405
416
|
"agentBoard.autopilot.reviewing": "오토파일럿 · 독립 리뷰 중",
|
|
@@ -419,6 +430,8 @@ const agentBoardKo = {
|
|
|
419
430
|
"agentBoard.autopilot.blocked.commitPrFailed": "오토파일럿 차단 · PR 요청 전송 실패",
|
|
420
431
|
"agentBoard.autopilot.blocked.noCompletionSignal": "오토파일럿 차단 · 완료 신호 없음",
|
|
421
432
|
"agentBoard.autopilot.blocked.loopDetected": "오토파일럿 차단 · 반복 감지로 중지됨",
|
|
433
|
+
"agentBoard.autopilot.blocked.qualityGateFailed": "품질 게이트 실패 · 같은 세션에서 수정 중",
|
|
434
|
+
"agentBoard.autopilot.blocked.qualityGateBlocked": "오토파일럿 차단 · 품질 게이트 실패",
|
|
422
435
|
"agentBoard.autopilot.loopStopFailed": "반복을 감지했지만 세션 중지에 실패했습니다: {detail}",
|
|
423
436
|
"agentBoard.autopilot.forceProceed": "그래도 진행",
|
|
424
437
|
"agentBoard.autopilot.forceProceedTitle": "리뷰어가 완료를 명시적으로 선언하지 않았습니다. 그래도 Commit & PR을 진행합니다.",
|
|
@@ -432,7 +445,12 @@ const agentBoardKo = {
|
|
|
432
445
|
"agentBoard.verify.phase.bootstrap": "준비",
|
|
433
446
|
"agentBoard.verify.phase.build": "빌드",
|
|
434
447
|
"agentBoard.verify.phase.test": "테스트",
|
|
435
|
-
"agentBoard.verify.phase.start": "구동 확인"
|
|
448
|
+
"agentBoard.verify.phase.start": "구동 확인",
|
|
449
|
+
"agentBoard.verify.detail.noRecipe": "실행할 레시피가 없습니다. package.json 스크립트, Makefile, 또는 .aplus/verify.json이 필요합니다.",
|
|
450
|
+
"agentBoard.verify.detail.phaseFailed": "{phase} 실패 · {reason}",
|
|
451
|
+
"agentBoard.verify.detail.phaseTimeout": "{phase} 시간 초과",
|
|
452
|
+
"agentBoard.verify.detail.notReady": "구동은 됐지만 포트가 응답하지 않습니다",
|
|
453
|
+
"agentBoard.verify.phase.deploy": "배포 확인"
|
|
436
454
|
};
|
|
437
455
|
const fanoutLifecycleKo = {
|
|
438
456
|
"fanoutSection.restoreGroupAria": "병렬 그룹 복원: {title} · 실행 {run}",
|
|
@@ -572,7 +590,7 @@ const orcaPortKo = {
|
|
|
572
590
|
"ciFix.promptIntro": "아래 PR의 CI가 실패했습니다. 실패 로그를 확인하고 원인을 수정해주세요:",
|
|
573
591
|
"shortcuts.action.quickOpen": "Quick Open 팔레트",
|
|
574
592
|
"shortcuts.action.quickOpenDescription": "프로젝트·세션·대화를 한 검색창에서 찾아 바로 이동합니다.",
|
|
575
|
-
"quickOpen.title": "
|
|
593
|
+
"quickOpen.title": "빠른 열기",
|
|
576
594
|
"quickOpen.inputAria": "Quick Open 검색",
|
|
577
595
|
"quickOpen.placeholder": "프로젝트, 세션, 대화 검색…",
|
|
578
596
|
"quickOpen.resultsAria": "Quick Open 결과",
|
|
@@ -623,6 +641,8 @@ const scheduledAutomationsKo = {
|
|
|
623
641
|
"scheduledAutomations.nameLabel": "이름",
|
|
624
642
|
"scheduledAutomations.promptLabel": "프롬프트",
|
|
625
643
|
"scheduledAutomations.agentLabel": "실행 에이전트",
|
|
644
|
+
"scheduledAutomations.personaLabel": "실행 페르소나",
|
|
645
|
+
"scheduledAutomations.personaNone": "없음",
|
|
626
646
|
"scheduledAutomations.scheduleLabel": "스케줄",
|
|
627
647
|
"scheduledAutomations.scheduleInterval": "주기 (분마다)",
|
|
628
648
|
"scheduledAutomations.scheduleDaily": "매일 지정 시각",
|
|
@@ -670,6 +690,8 @@ const scheduledAutomationsKo = {
|
|
|
670
690
|
"scheduledAutomations.outcomeSkippedOverlap": "이전 실행 진행 중(스킵)",
|
|
671
691
|
"scheduledAutomations.outcomeSilent": "이상 없음([SILENT])",
|
|
672
692
|
"scheduledAutomations.outcomeError": "실패",
|
|
693
|
+
"scheduledAutomations.requestedModel": "요청 모델: {model}",
|
|
694
|
+
"scheduledAutomations.fallbackModelUsed": "폴백 모델 사용됨: {model}",
|
|
673
695
|
"scheduledAutomations.validationRequired": "이름과 프롬프트를 입력하세요.",
|
|
674
696
|
"scheduledAutomations.migrationTitle": "기존 자동화 이전",
|
|
675
697
|
"scheduledAutomations.migrationHint": "기존 앱·daemon 자동화를 Happy Server로 안전하게 이전합니다.",
|
|
@@ -753,6 +775,10 @@ const githubTriggersKo = {
|
|
|
753
775
|
"githubTriggers.actionLabel": "액션",
|
|
754
776
|
"githubTriggers.actionNotify": "알림만 (LLM 비용 없음)",
|
|
755
777
|
"githubTriggers.actionStartSession": "세션 시작",
|
|
778
|
+
"githubTriggers.actionVerifyMerge": "머지 검증 (실패 시에만 세션)",
|
|
779
|
+
"mergeVerify.noticePassed": 'PR #{pr} "{title}" 머지 후 검증을 통과했습니다 ({revision})',
|
|
780
|
+
"mergeVerify.revisionMergeCommit": "머지 커밋 {sha}",
|
|
781
|
+
"mergeVerify.revisionBaseFallback": "base 브랜치 tip — 머지 커밋을 확인하지 못해 폴백",
|
|
756
782
|
"githubTriggers.agentLabel": "실행 에이전트",
|
|
757
783
|
"githubTriggers.modelLabel": "모델 (비우면 자동)",
|
|
758
784
|
"githubTriggers.effortLabel": "추론 강도",
|
|
@@ -781,13 +807,15 @@ const githubTriggersKo = {
|
|
|
781
807
|
"githubTriggers.outcomeSessionStarted": "세션 시작",
|
|
782
808
|
"githubTriggers.outcomeSkippedOverlap": "이전 세션 진행 중(스킵)",
|
|
783
809
|
"githubTriggers.outcomeError": "실패",
|
|
810
|
+
"githubTriggers.outcomeVerifyPassed": "검증 통과",
|
|
811
|
+
"githubTriggers.outcomeVerifyFailed": "검증 실패 → 수정 세션",
|
|
784
812
|
"githubTriggers.pollError": "GitHub 조회 실패: {detail} — 다음 주기에 재시도합니다.",
|
|
785
813
|
"githubTriggers.validationRequired": "이름과 템플릿을 입력하세요."
|
|
786
814
|
};
|
|
787
815
|
const memoryHubKo = {
|
|
788
816
|
"memoryHub.button": "메모리",
|
|
789
817
|
"memoryHub.ariaLabel": "메모리 허브",
|
|
790
|
-
"memoryHub.title": "
|
|
818
|
+
"memoryHub.title": "메모리 허브",
|
|
791
819
|
"memoryHub.refresh": "새로고침",
|
|
792
820
|
"memoryHub.openDashboard": "전체 대시보드 열기",
|
|
793
821
|
"memoryHub.loading": "프로젝트 메모리를 불러오는 중…",
|
|
@@ -816,12 +844,33 @@ const extensionPanelKo = {
|
|
|
816
844
|
};
|
|
817
845
|
const extensionInstallKo = {
|
|
818
846
|
"extensions.installOfficial": "확장 설치",
|
|
847
|
+
"extensions.installArtifactPublisher": "공개 링크 확장 설치",
|
|
819
848
|
"extensions.installDescription": "계속하려면 공식 확장을 설치하세요.",
|
|
820
849
|
"extensions.machineExecutePermission": "이 확장은 미리 정의된 동작에만 machine.execute 권한을 요청합니다.",
|
|
821
850
|
"extensions.notInstalled": "요청한 확장이 설치되지 않았습니다.",
|
|
822
851
|
"extensions.disabled": "요청한 확장이 비활성화되어 있습니다.",
|
|
823
852
|
"extensions.quarantined": "요청한 확장이 반복된 오류로 격리되었습니다.",
|
|
824
|
-
"extensions.unavailable": "요청한 확장이 설치되어 있지만 현재 사용할 수 없습니다."
|
|
853
|
+
"extensions.unavailable": "요청한 확장이 설치되어 있지만 현재 사용할 수 없습니다.",
|
|
854
|
+
"extensions.permission.artifactPublishPublic": "선택한 결과물의 공개 링크를 만들 수 있습니다. 파일 내용과 링크 처리는 Saycode Core가 담당합니다.",
|
|
855
|
+
"artifactPublication.dialogTitle": "공개 링크",
|
|
856
|
+
"artifactPublication.copy": "링크 복사",
|
|
857
|
+
"artifactPublication.copied": "복사됨",
|
|
858
|
+
"artifactPublication.open": "링크 열기",
|
|
859
|
+
"artifactPublication.revoke": "공개 중지",
|
|
860
|
+
"artifactPublication.revoking": "중지 중…",
|
|
861
|
+
"artifactPublication.failed": "공개 링크 작업에 실패했습니다.",
|
|
862
|
+
"artifactPublication.error.failed": "공개 링크 작업에 실패했습니다.",
|
|
863
|
+
"artifactPublication.error.cancelled": "공개 링크 작업이 취소되었습니다.",
|
|
864
|
+
"artifactPublication.error.timeout": "서버 응답 시간이 초과되었습니다. 잠시 후 다시 시도해 주세요.",
|
|
865
|
+
"artifactPublication.error.network": "서버에 연결할 수 없습니다. 네트워크 상태를 확인해 주세요.",
|
|
866
|
+
"artifactPublication.error.unauthenticated": "로그인 세션이 만료되었습니다. 다시 로그인한 뒤 시도해 주세요.",
|
|
867
|
+
"artifactPublication.error.notFound": "파일을 찾을 수 없습니다. 파일이 저장되었는지 확인해 주세요.",
|
|
868
|
+
"artifactPublication.error.tooLarge": "파일이 너무 큽니다. 공개할 HTML 파일은 10MB 이하여야 합니다.",
|
|
869
|
+
"artifactPublication.error.rateLimited": "공개 링크 요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.",
|
|
870
|
+
"artifactPublication.error.rejected": "서버가 이 파일의 공개 요청을 거부했습니다.",
|
|
871
|
+
"artifactPublication.error.serverError": "서버 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
|
872
|
+
"artifactPublication.error.extensionUnavailable": "확장이 비활성화되었거나 공개 권한이 없습니다. 설정에서 Artifact Publisher를 확인해 주세요.",
|
|
873
|
+
"artifactPublication.error.invalidResponse": "서버 응답을 확인할 수 없어 중단했습니다."
|
|
825
874
|
};
|
|
826
875
|
const personalChatsLandingKo = {
|
|
827
876
|
"personalChats.heroTitle": "무엇을 도와드릴까요?",
|
|
@@ -855,7 +904,7 @@ const personalChatsKo = {
|
|
|
855
904
|
"personalChats.creating": "채팅을 시작하는 중…",
|
|
856
905
|
"personalChats.noOnlineMachine": "온라인 머신이 없습니다. 채팅을 시작하려면 머신을 시작해주세요.",
|
|
857
906
|
"personalChats.localMachinePreparing": "이 컴퓨터를 Saycode에 연결하는 중이에요. 잠시 후 바로 시작할 수 있어요.",
|
|
858
|
-
"personalChats.localMachineAsk": "이
|
|
907
|
+
"personalChats.localMachineAsk": "이 컴퓨터에 Saycode CLI를 설치하면 바로 채팅을 시작할 수 있어요.",
|
|
859
908
|
"personalChats.localMachineFailed": "이 컴퓨터 자동 연결에 실패했어요. 다른 방법으로 연결할 수 있어요.",
|
|
860
909
|
"personalChats.pickMachine": "이 채팅을 실행할 머신을 선택하세요",
|
|
861
910
|
"personalChats.back": "뒤로",
|
|
@@ -919,16 +968,45 @@ const agentLoopDetectionKo = {
|
|
|
919
968
|
"agentBoard.loopDetectedTitle": "같은 툴 호출 또는 텍스트가 짧은 시간 안에 반복되고 있습니다. 멈춰 있을 수 있습니다.",
|
|
920
969
|
"settings.agentLoopAutoStop.enable": "반복 감지 시 세션 자동 중지",
|
|
921
970
|
"settings.agentLoopAutoStop.help": "오토파일럿이 관리하는 세션이 같은 툴 호출이나 텍스트를 반복하며 멈춘 것으로 보이면 자동으로 응답을 중지합니다. 기본은 꺼짐 — 오탐으로 정상 작업을 멈추는 것이 놓치는 것보다 나쁩니다.",
|
|
922
|
-
"settings.agentLoopAutoStop.saveFailed": "설정을 저장하지 못했습니다."
|
|
971
|
+
"settings.agentLoopAutoStop.saveFailed": "설정을 저장하지 못했습니다.",
|
|
972
|
+
"settings.autonomousQualityGate.enable": "오토파일럿 자율 품질 게이트 사용",
|
|
973
|
+
"settings.autonomousQualityGate.help": "리뷰와 Commit & PR 전에 프로젝트 검증을 실행하고, 실패하면 같은 세션에 제한된 수정 요청을 보냅니다. 기본은 꺼짐입니다.",
|
|
974
|
+
"settings.autonomousQualityGate.saveFailed": "품질 게이트 설정을 저장하지 못했습니다.",
|
|
975
|
+
"qualityGate.statusAria": "자율 품질 게이트 상태",
|
|
976
|
+
"qualityGate.stage": "품질 게이트 · {stage}",
|
|
977
|
+
"qualityGate.attempt": "시도 {attempt}/{max}",
|
|
978
|
+
"qualityGate.usage": "수정 {continuations}/{maxContinuations} · 턴 {turns}/{maxTurns} · 토큰 {tokens}/{maxTokens}",
|
|
979
|
+
"qualityGate.lastPhase": "{phase} · {result}",
|
|
980
|
+
"qualityGate.limit": "한도 도달: {reason}",
|
|
981
|
+
"qualityGate.pause": "품질 게이트 일시정지",
|
|
982
|
+
"qualityGate.resume": "품질 게이트 재개",
|
|
983
|
+
"qualityGate.stop": "품질 게이트 중지"
|
|
923
984
|
};
|
|
924
985
|
const autopilotSelfReviewRoundsKo = {
|
|
925
986
|
"settings.autopilotSelfReviewRounds.label": "오토파일럿 셀프 리뷰 반복 횟수",
|
|
926
|
-
"settings.autopilotSelfReviewRounds.help": '오토파일럿이 "현재 에이전트로 점검"을 몇 번까지 이어서 시킬지 정합니다.
|
|
987
|
+
"settings.autopilotSelfReviewRounds.help": '오토파일럿이 "현재 에이전트로 점검"을 몇 번까지 이어서 시킬지 정합니다. 한 라운드에서 medium 이상 지적이 나오면 남은 횟수만큼 다시 점검하게 하고, low 이하만 남으면 남은 횟수와 상관없이 바로 Commit & PR로 넘어갑니다. 지정한 횟수를 다 쓰고도 medium 이상이 남으면 멈추고 사람에게 넘깁니다. 기본은 7회.',
|
|
927
988
|
"settings.autopilotSelfReviewRounds.once": "1회 (반복 없음)",
|
|
928
989
|
"settings.autopilotSelfReviewRounds.times": "{count}회",
|
|
929
990
|
"settings.autopilotSelfReviewRounds.saveFailed": "설정을 저장하지 못했습니다.",
|
|
930
991
|
"agentBoard.autopilot.reviewingSelfRound": "오토파일럿 · 셀프 리뷰 중 {round}/{total}"
|
|
931
992
|
};
|
|
993
|
+
const memoryGuardrailsKo = {
|
|
994
|
+
"settings.memoryGuardrails.help": "기억으로 남기지 말아야 할 항목을 정합니다. 기본 규칙은 끌 수 있고, 프로젝트 전용 규칙을 추가할 수 있습니다.",
|
|
995
|
+
"settings.memoryGuardrails.saveFailed": "설정을 저장하지 못했습니다.",
|
|
996
|
+
"settings.memoryGuardrails.tooLong": "규칙 전체 길이가 {max}자를 넘어 저장할 수 없습니다.",
|
|
997
|
+
"settings.memoryGuardrails.removeAria": "{text} 삭제",
|
|
998
|
+
"settings.memoryGuardrails.remove": "삭제",
|
|
999
|
+
"settings.memoryGuardrails.addLabel": "새 금지 항목",
|
|
1000
|
+
"settings.memoryGuardrails.addPlaceholder": "예: 이 프로젝트의 임시 브랜치 이름은 기억하지 않는다",
|
|
1001
|
+
"settings.memoryGuardrails.add": "추가"
|
|
1002
|
+
};
|
|
1003
|
+
const memoryEventsKo = {
|
|
1004
|
+
"settings.memoryEvents.ariaLabel": "기억 열람",
|
|
1005
|
+
"settings.memoryEvents.unavailable": "기억 대시보드가 실행 중이 아닙니다.",
|
|
1006
|
+
"settings.memoryEvents.searchLabel": "기억 검색",
|
|
1007
|
+
"settings.memoryEvents.prevPage": "이전",
|
|
1008
|
+
"settings.memoryEvents.nextPage": "다음"
|
|
1009
|
+
};
|
|
932
1010
|
const externalWorktreeInboxKo = {
|
|
933
1011
|
"externalWorktreeInbox.title": "앱 밖에서 만든 worktree",
|
|
934
1012
|
"externalWorktreeInbox.description": "이 저장소에 앱이 만들지 않은 worktree 가 있습니다. 채택하면 목록과 새 대화 시작에서 고를 수 있어요.",
|
|
@@ -959,6 +1037,99 @@ const externalSessionVaultKo = {
|
|
|
959
1037
|
"externalSessionVault.rejectedNotOwned": "이 프로젝트 폴더에서 돌린 대화가 아니라서 이어가지 않았습니다.",
|
|
960
1038
|
"externalSessionVault.resumeFailed": "그 대화를 이어가지 못했습니다."
|
|
961
1039
|
};
|
|
1040
|
+
const orgSearchKo = {
|
|
1041
|
+
"orgAdmin.tab.search": "검색 (Elasticsearch)",
|
|
1042
|
+
"orgSearch.admin.title": "Elasticsearch 기반 조직 검색",
|
|
1043
|
+
"orgSearch.admin.description": "조직의 대화를 조직 소유 Elasticsearch에 색인해 멤버가 키워드로 검색할 수 있게 합니다. 각 멤버는 자기가 참여한 프로젝트만 볼 수 있으며, 이는 멤버별 필터 alias와 API 키로 Elasticsearch가 직접 강제합니다.",
|
|
1044
|
+
"orgSearch.admin.unsupported": "이 서버 또는 데스크탑 버전은 아직 조직 검색을 지원하지 않습니다.",
|
|
1045
|
+
"orgSearch.admin.readOnly": "조직 owner/admin만 이 연동을 변경할 수 있습니다.",
|
|
1046
|
+
"orgSearch.form.url": "Elasticsearch 주소",
|
|
1047
|
+
"orgSearch.form.urlPlaceholder": "http://elasticsearch.internal:9200",
|
|
1048
|
+
"orgSearch.form.username": "관리자 ID",
|
|
1049
|
+
"orgSearch.form.password": "관리자 비밀번호",
|
|
1050
|
+
"orgSearch.form.passwordHint": "전용 운영자 사용자를 만드는 데 한 번만 쓰입니다. 관리자 비밀번호는 저장하지 않습니다.",
|
|
1051
|
+
"orgSearch.form.profile": "analyzer 프로필",
|
|
1052
|
+
"orgSearch.form.profile.nori": "nori + ngram (한국어 형태소, 권장)",
|
|
1053
|
+
"orgSearch.form.profile.standard": "standard + ngram (nori 플러그인 없음)",
|
|
1054
|
+
"orgSearch.form.test": "연결 테스트",
|
|
1055
|
+
"orgSearch.form.testing": "테스트 중…",
|
|
1056
|
+
"orgSearch.form.save": "연결하고 인덱스 만들기",
|
|
1057
|
+
"orgSearch.form.saving": "연결 중…",
|
|
1058
|
+
"orgSearch.probe.version": "Elasticsearch 버전",
|
|
1059
|
+
"orgSearch.probe.security": "보안 활성",
|
|
1060
|
+
"orgSearch.probe.license": "라이선스",
|
|
1061
|
+
"orgSearch.probe.nori": "analysis-nori 플러그인",
|
|
1062
|
+
"orgSearch.probe.privileges": "보안 관리 권한 (운영자 사용자 생성)",
|
|
1063
|
+
"orgSearch.probe.yes": "예",
|
|
1064
|
+
"orgSearch.probe.no": "아니오",
|
|
1065
|
+
"orgSearch.probe.unknown": "알 수 없음",
|
|
1066
|
+
"orgSearch.verdict.ok": "연결에 성공했습니다. 저장하면 운영자 사용자와 인덱스를 만듭니다.",
|
|
1067
|
+
"orgSearch.verdict.unreachable": "Elasticsearch에 닿을 수 없습니다. URL과 이 머신이 같은 네트워크(VPN)에 있는지 확인하세요.",
|
|
1068
|
+
"orgSearch.verdict.unauthorized": "인증에 실패했습니다. 관리자 ID와 비밀번호를 확인하세요.",
|
|
1069
|
+
"orgSearch.verdict.unsupported-version": "Elasticsearch 8 이상이 필요합니다.",
|
|
1070
|
+
"orgSearch.verdict.security-disabled": "이 클러스터는 보안이 꺼져 있어 프로젝트별 접근 권한을 강제할 수 없습니다. xpack.security를 먼저 켜세요.",
|
|
1071
|
+
"orgSearch.verdict.insufficient-privileges": "이 사용자는 보안을 관리할 수 없습니다. manage_security와 manage_api_key 권한이 있는 사용자를 쓰세요.",
|
|
1072
|
+
"orgSearch.saved.title": "조직 검색이 연결되었습니다.",
|
|
1073
|
+
"orgSearch.saved.detail": "운영자 사용자 {operator} · 인덱스 {index}",
|
|
1074
|
+
"orgSearch.saved.existingIndex": "매핑이 일치하는 기존 인덱스를 그대로 사용했습니다.",
|
|
1075
|
+
"orgSearch.summary.url": "Elasticsearch 주소",
|
|
1076
|
+
"orgSearch.summary.index": "인덱스",
|
|
1077
|
+
"orgSearch.summary.profile": "analyzer 프로필",
|
|
1078
|
+
"orgSearch.summary.operator": "운영자 사용자",
|
|
1079
|
+
"orgSearch.summary.updated": "갱신",
|
|
1080
|
+
"orgSearch.disconnect": "연동 해제",
|
|
1081
|
+
"orgSearch.disconnect.title": "조직 검색 연동을 해제할까요?",
|
|
1082
|
+
"orgSearch.disconnect.body": "Elasticsearch에서 멤버 키·alias·조직 인덱스를 삭제하고, 멤버의 조직 검색 탭은 즉시 사라집니다. 대화 자체에는 영향이 없습니다.",
|
|
1083
|
+
"orgSearch.disconnect.operatorSection": "운영자 사용자도 삭제 (관리자 계정이 다시 필요)",
|
|
1084
|
+
"orgSearch.disconnect.confirm": "해제하고 삭제",
|
|
1085
|
+
"orgSearch.disconnect.working": "해제 중…",
|
|
1086
|
+
"orgSearch.disconnect.cancel": "취소",
|
|
1087
|
+
"orgSearch.disconnected": "해제했습니다. 인덱스 {indices}개, alias {aliases}개, 멤버 키 {keys}개를 삭제했습니다.",
|
|
1088
|
+
"orgSearch.disconnected.operatorKept": "관리자 자격증명이 없어 운영자 사용자는 남겨 두었습니다.",
|
|
1089
|
+
"orgSearch.error.title": "마지막 작업이 실패했습니다",
|
|
1090
|
+
"orgSearch.acl.title": "권한 동기화 (멤버별 alias·키)",
|
|
1091
|
+
"orgSearch.acl.upToDate": "권한이 최신입니다",
|
|
1092
|
+
"orgSearch.acl.waiting": "{count}건 대기",
|
|
1093
|
+
"orgSearch.acl.members": "멤버 {count}명",
|
|
1094
|
+
"orgSearch.acl.tick.applied": "마지막 동기화: 변경 {actions}건 적용, 실패 {failures}건.",
|
|
1095
|
+
"orgSearch.acl.tick.inSync": "마지막 동기화에서 변경할 것이 없었습니다.",
|
|
1096
|
+
"orgSearch.acl.tick.elsewhere": "다른 관리자의 데스크탑이 동기화를 맡고 있습니다.",
|
|
1097
|
+
"orgSearch.acl.tick.failed": "마지막 동기화 실패: {message}",
|
|
1098
|
+
"orgSearch.progress.title": "색인 진행률",
|
|
1099
|
+
"orgSearch.progress.phase.idle": "색인할 대화 없음",
|
|
1100
|
+
"orgSearch.progress.phase.waiting": "색인할 멤버 대기 중",
|
|
1101
|
+
"orgSearch.progress.phase.indexing": "색인 중",
|
|
1102
|
+
"orgSearch.progress.phase.done": "색인 완료",
|
|
1103
|
+
"orgSearch.progress.sessions": "세션 {done}/{total} ({percent}%)",
|
|
1104
|
+
"orgSearch.progress.messages": "메시지 {count}개 색인됨",
|
|
1105
|
+
"orgSearch.progress.failed": "실패 {count}건",
|
|
1106
|
+
"orgSearch.progress.leases": "활성 색인기 {count}개",
|
|
1107
|
+
"orgSearch.progress.updated": "{time} 갱신",
|
|
1108
|
+
"orgSearch.progress.project": "프로젝트",
|
|
1109
|
+
"orgSearch.progress.phaseColumn": "상태",
|
|
1110
|
+
"orgSearch.progress.sessionsColumn": "세션",
|
|
1111
|
+
"orgSearch.progress.leader": "색인기",
|
|
1112
|
+
"search.tabOrg": "조직",
|
|
1113
|
+
"orgSearch.tab.keyPending": "조직 관리자의 데스크탑이 검색 키를 발급하는 중입니다. 잠시 후 다시 시도하세요.",
|
|
1114
|
+
"orgSearch.tab.unavailable": "지금은 조직 검색을 사용할 수 없습니다.",
|
|
1115
|
+
"orgSearch.tab.error.unauthorized": "검색 키가 거부되었습니다. 새 키를 요청했으니 잠시 후 다시 시도하세요.",
|
|
1116
|
+
"orgSearch.tab.error.forbidden": "Elasticsearch가 이 검색을 거부했습니다. 권한 동기화가 늦었을 수 있으며 관리자 데스크탑이 곧 맞춥니다.",
|
|
1117
|
+
"orgSearch.tab.error.unreachable": "조직 Elasticsearch에 닿을 수 없습니다. 같은 네트워크(VPN)에 있는지 확인하세요.",
|
|
1118
|
+
"orgSearch.tab.error.timeout": "Elasticsearch가 제때 응답하지 않았습니다.",
|
|
1119
|
+
"orgSearch.tab.error.not-configured": "이 데스크탑에는 아직 조직 검색이 설정되지 않았습니다.",
|
|
1120
|
+
"orgSearch.tab.error.error": "검색에 실패했습니다.",
|
|
1121
|
+
"orgSearch.eval.title": "검색 품질 평가",
|
|
1122
|
+
"orgSearch.eval.description": "색인된 내 프롬프트 {count}건을 표본으로 뽑아 실제 검색어처럼 변형(부분 문자열·키워드·오타·식별자 등)한 뒤, 각 검색어가 자기 대화를 찾는지를 Elasticsearch와 이 데스크탑의 로컬 인덱스에서 나란히 확인합니다. 프롬프트는 앱 밖으로 나가지 않습니다.",
|
|
1123
|
+
"orgSearch.eval.run": "품질 평가 실행",
|
|
1124
|
+
"orgSearch.eval.running": "평가 중…",
|
|
1125
|
+
"orgSearch.eval.emptyIndex": "로컬 인덱스에 표본으로 쓸 프롬프트가 아직 없습니다.",
|
|
1126
|
+
"orgSearch.eval.sampled": "프롬프트 {prompts}건, 검색어 {cases}건",
|
|
1127
|
+
"orgSearch.eval.kind": "변형 종류",
|
|
1128
|
+
"orgSearch.eval.engine.es": "Elasticsearch(조직)",
|
|
1129
|
+
"orgSearch.eval.engine.local": "로컬 인덱스",
|
|
1130
|
+
"orgSearch.eval.overall": "전체",
|
|
1131
|
+
"orgSearch.eval.legend": "칸: Recall@1 / @5 / @10 · MRR · p95 지연. 같은 문구를 여러 대화에서 썼다면 다른 대화가 1위인 것도 정상이라, 100% 미만이 곧 결함은 아닙니다."
|
|
1132
|
+
};
|
|
962
1133
|
const operationModelRetryKo = {
|
|
963
1134
|
"review.job.fallbackModelUsed": "폴백 모델 사용됨: {model}"
|
|
964
1135
|
};
|
|
@@ -1097,7 +1268,7 @@ const machineSettingsConsoleKo = {
|
|
|
1097
1268
|
"machineConsole.daemonUnknown": "daemon 미확인",
|
|
1098
1269
|
"machineConsole.daemonStatus": "daemon 상태",
|
|
1099
1270
|
"machineConsole.daemonVersion": "daemon 버전",
|
|
1100
|
-
"machineConsole.daemonVersionValue": "
|
|
1271
|
+
"machineConsole.daemonVersionValue": "데몬 {version}",
|
|
1101
1272
|
"machineConsole.rpcStatus": "RPC 상태",
|
|
1102
1273
|
"machineConsole.rpcAvailableStatus": "RPC 사용 가능",
|
|
1103
1274
|
"machineConsole.rpcUnavailableStatus": "RPC 사용 불가",
|
|
@@ -1132,7 +1303,9 @@ const machineSettingsConsoleKo = {
|
|
|
1132
1303
|
"machineConsole.updateTimeout": "업데이트가 시작됐지만 완료를 확인하지 못했습니다. 잠시 후 다시 진단해 주세요.",
|
|
1133
1304
|
"machineConsole.updateFailed": "CLI 업데이트에 실패했습니다.",
|
|
1134
1305
|
"machineConsole.aiCliStarted": "Codex와 Claude Code 설치를 시작했습니다.",
|
|
1135
|
-
"machineConsole.aiCliFailed": "AI CLI 설치를 시작하지 못했습니다."
|
|
1306
|
+
"machineConsole.aiCliFailed": "AI CLI 설치를 시작하지 못했습니다.",
|
|
1307
|
+
"machineConsole.machineActionFailed": "머신 작업에 실패했습니다.",
|
|
1308
|
+
"machineConsole.extensionCommandsUnavailable": "Extension 명령을 사용할 수 없습니다."
|
|
1136
1309
|
};
|
|
1137
1310
|
const sessionTokenUsageKo = {
|
|
1138
1311
|
"sessionTokenUsage.total": "누적 {count} 토큰",
|
|
@@ -1183,7 +1356,17 @@ const newSessionProfileKo = {
|
|
|
1183
1356
|
"newSession.profile.fallbackAgent": "{value} 에이전트를 지금 쓸 수 없어 현재 선택을 유지했습니다.",
|
|
1184
1357
|
"newSession.profile.fallbackModel": "{value} 모델이 목록에 없어 기본값으로 시작합니다.",
|
|
1185
1358
|
"newSession.profile.fallbackEffort": "{value} 강도를 이 모델에서 쓸 수 없어 기본값으로 시작합니다.",
|
|
1186
|
-
"newSession.profile.fallbackWorktree": "이 프로젝트는 git 저장소가 아니라 작업 사본을 끕니다."
|
|
1359
|
+
"newSession.profile.fallbackWorktree": "이 프로젝트는 git 저장소가 아니라 작업 사본을 끕니다.",
|
|
1360
|
+
// specs/desktop-session-personas T9
|
|
1361
|
+
"newSession.profile.editPersona": "{name} 페르소나 편집",
|
|
1362
|
+
"newSession.profile.instructionsLabel": "{name} 지시문",
|
|
1363
|
+
"newSession.profile.instructionsPlaceholder": "예: 리뷰 관점에서만 답하라",
|
|
1364
|
+
"newSession.profile.instructionsTooLong": "지시문이 너무 깁니다 ({max}자 이하로 줄여주세요).",
|
|
1365
|
+
"newSession.profile.instructionsSave": "{name} 지시문 저장",
|
|
1366
|
+
"newSession.profile.skillsLabel": "배포할 스킬",
|
|
1367
|
+
"newSession.profile.badgeIconLabel": "{name} 표식 아이콘",
|
|
1368
|
+
"newSession.profile.badgeColorLabel": "{name} 표식 색상",
|
|
1369
|
+
"newSession.profile.badgeSave": "{name} 표식 저장"
|
|
1187
1370
|
};
|
|
1188
1371
|
const agentBoardBroadcastKo = {
|
|
1189
1372
|
"agentBoard.broadcastSelected": "선택됨",
|
|
@@ -1241,6 +1424,16 @@ const officeViewerKo = {
|
|
|
1241
1424
|
"officeViewer.unsupported": "미리보기를 지원하지 않는 형식입니다.",
|
|
1242
1425
|
"officeViewer.unsupportedHint": "원본 앱에서 열어 확인하세요."
|
|
1243
1426
|
};
|
|
1427
|
+
const additionalDirectoryMountWarningKo = {
|
|
1428
|
+
"additionalDirectories.mountMissing": "추가 디렉터리 {count}개를 자동 마운트에서 제외하고 세션을 시작했습니다.",
|
|
1429
|
+
"additionalDirectories.mountRuntimeUnsupported": "이 머신의 Happy CLI가 추가 디렉터리 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
|
|
1430
|
+
"additionalDirectories.mountLimit": "추가 디렉터리 마운트 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다.",
|
|
1431
|
+
"additionalDirectories.relatedMountMissing": "관련 프로젝트 디렉터리 {count}개를 자동 마운트에서 제외하고 세션을 시작했습니다.",
|
|
1432
|
+
"additionalDirectories.relatedMountLinkLookupFailed": "관련 프로젝트 목록을 불러오지 못해 자동 마운트 없이 세션을 시작했습니다.",
|
|
1433
|
+
"additionalDirectories.relatedMountLookupFailed": "관련 프로젝트 {count}개를 불러오지 못해 제외하고 세션을 시작했습니다.",
|
|
1434
|
+
"additionalDirectories.relatedMountRuntimeUnsupported": "이 머신의 Happy CLI가 관련 프로젝트 자동 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
|
|
1435
|
+
"additionalDirectories.relatedMountLimit": "관련 프로젝트 디렉터리 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다."
|
|
1436
|
+
};
|
|
1244
1437
|
const additionalDirectoriesKo = {
|
|
1245
1438
|
"additionalDirectories.title": "추가 디렉터리",
|
|
1246
1439
|
"additionalDirectories.add": "디렉터리 추가",
|
|
@@ -1260,7 +1453,8 @@ const additionalDirectoriesKo = {
|
|
|
1260
1453
|
"additionalDirectories.limitReached": "추가 디렉터리는 최대 8개까지 등록할 수 있습니다.",
|
|
1261
1454
|
"additionalDirectories.machineMismatch": "현재 머신과 다른 위치에서 선택한 디렉터리입니다.",
|
|
1262
1455
|
"additionalDirectories.saveFailed": "디렉터리를 등록하지 못했습니다.",
|
|
1263
|
-
"additionalDirectories.folderLoadFailed": "디렉터리를 불러오지 못했습니다."
|
|
1456
|
+
"additionalDirectories.folderLoadFailed": "디렉터리를 불러오지 못했습니다.",
|
|
1457
|
+
...additionalDirectoryMountWarningKo
|
|
1264
1458
|
};
|
|
1265
1459
|
const agentHandoffKo = {
|
|
1266
1460
|
"agentHandoff.open": "다른 에이전트로 이어가기",
|
|
@@ -1332,6 +1526,33 @@ const subagentKo = {
|
|
|
1332
1526
|
const reviewNavigationKo = {
|
|
1333
1527
|
"review.action.openSourceSession": "원본 대화로 이동: {title}"
|
|
1334
1528
|
};
|
|
1529
|
+
const chatFolderUploadKo = {
|
|
1530
|
+
"upload.kindFolder": "폴더",
|
|
1531
|
+
"upload.folderReference": "{path} ({count}개 파일)",
|
|
1532
|
+
"upload.folderChip": "{name} · {count}개 파일",
|
|
1533
|
+
"upload.folderEmpty": "폴더에 업로드할 파일이 없습니다: {name}",
|
|
1534
|
+
"upload.progress": "파일 업로드 중 {done}/{total}",
|
|
1535
|
+
"upload.partialIssue": "업로드 실패 {failed}개 · 건너뜀 {skipped}개",
|
|
1536
|
+
"upload.invalidPath": "안전하지 않은 첨부 경로입니다."
|
|
1537
|
+
};
|
|
1538
|
+
const composerAddMenuKo = {
|
|
1539
|
+
"composer.addMenu.trigger": "추가",
|
|
1540
|
+
"composer.addMenu.fileOrPhoto": "파일 또는 사진 추가",
|
|
1541
|
+
"composer.addMenu.folder": "폴더 추가",
|
|
1542
|
+
"composer.addMenu.disabled": "지금은 파일이나 폴더를 추가할 수 없습니다.",
|
|
1543
|
+
"shortcuts.action.composerAttachFile": "파일 또는 사진 추가",
|
|
1544
|
+
"shortcuts.action.composerAttachFileDescription": "포커스된 메시지 작성란의 파일 선택기를 엽니다."
|
|
1545
|
+
};
|
|
1546
|
+
const composerQuickCommandsKo = {
|
|
1547
|
+
"composer.quickCommands.trigger": "Quick Commands",
|
|
1548
|
+
"composer.quickCommands.saveCurrent": "현재 입력을 이 프로젝트의 Quick Command로 저장",
|
|
1549
|
+
"composer.quickCommands.saveCurrentGlobal": "현재 입력을 모든 프로젝트의 Quick Command로 저장",
|
|
1550
|
+
"composer.quickCommands.storageWriteFailed": "이 기기에 저장하지 못했습니다 — 앱을 다시 시작하면 목록이 사라질 수 있습니다.",
|
|
1551
|
+
"composer.quickCommands.empty": "저장된 Quick Command가 없습니다.",
|
|
1552
|
+
"composer.quickCommands.projectScope": "이 프로젝트",
|
|
1553
|
+
"composer.quickCommands.globalScope": "모든 프로젝트",
|
|
1554
|
+
"composer.quickCommands.remove": '"{label}" 삭제'
|
|
1555
|
+
};
|
|
1335
1556
|
const aiCommitPrKo = {
|
|
1336
1557
|
"aiCommitPr.stagedCount": "스테이지된 파일 {count}개",
|
|
1337
1558
|
"aiCommitPr.noStaged": "스테이지된 변경이 없습니다.",
|
|
@@ -1404,8 +1625,187 @@ const projectLinksKo = {
|
|
|
1404
1625
|
"projectLinks.mcpToggleHint": "켜면 AI가 관련 프로젝트로 작업을 제안했을 때 확인 없이 바로 진행됩니다.",
|
|
1405
1626
|
"projectLinks.mcpToggleFailed": "설정을 바꾸지 못했습니다."
|
|
1406
1627
|
};
|
|
1407
|
-
const
|
|
1628
|
+
const connectorReadinessKo = {
|
|
1629
|
+
"connectorReadiness.badge": "커넥터 {count}건 확인 필요",
|
|
1630
|
+
"connectorReadiness.state.needsAuth": "다시 연결 필요",
|
|
1631
|
+
"connectorReadiness.state.runtimeFailed": "연결 실패",
|
|
1632
|
+
"connectorReadiness.state.configMissing": "설정 없음",
|
|
1633
|
+
"connectorReadiness.state.unknown": "상태 미확인"
|
|
1634
|
+
};
|
|
1635
|
+
const checkpointKo = {
|
|
1636
|
+
"checkpoint.title": "파일 체크포인트",
|
|
1637
|
+
"checkpoint.status.protected": "보호됨",
|
|
1638
|
+
"checkpoint.status.legacy": "보호되지 않음",
|
|
1639
|
+
"checkpoint.status.unavailable": "보호 사용 불가",
|
|
1640
|
+
"checkpoint.refresh": "새로고침",
|
|
1641
|
+
"checkpoint.timeline": "기록",
|
|
1642
|
+
"checkpoint.timeline.empty": "아직 체크포인트가 없습니다.",
|
|
1643
|
+
"checkpoint.timeline.snapshot": "스냅샷",
|
|
1644
|
+
"checkpoint.timeline.rewind": "복원",
|
|
1645
|
+
"checkpoint.preview": "미리보기",
|
|
1646
|
+
"checkpoint.preview.title": "복원 미리보기",
|
|
1647
|
+
"checkpoint.preview.empty": "되돌릴 파일 변경이 없습니다.",
|
|
1648
|
+
"checkpoint.preview.confirm": "이 미리보기의 파일 변경을 확인했습니다",
|
|
1649
|
+
"checkpoint.restore": "파일 상태 복원",
|
|
1650
|
+
"checkpoint.restore.running": "파일 상태를 복원하는 중…",
|
|
1651
|
+
"checkpoint.restore.completed": "파일 상태 복원이 완료되었습니다.",
|
|
1652
|
+
"checkpoint.restore.partial": "일부 파일을 복원하지 못했습니다.",
|
|
1653
|
+
"checkpoint.restore.stale": "파일 상태가 바뀌었습니다. 새 미리보기를 확인하세요.",
|
|
1654
|
+
"checkpoint.restore.retry": "실패한 복원 다시 시도",
|
|
1655
|
+
"checkpoint.restore.cancel": "복원 취소",
|
|
1656
|
+
"checkpoint.pending.title": "보호 정책 확인 필요",
|
|
1657
|
+
"checkpoint.pending.partialWarning": "이번 turn이 일부 실행되었을 수 있습니다.",
|
|
1658
|
+
"checkpoint.pending.externalWarning": "외부 부작용은 다시 실행될 수 있습니다.",
|
|
1659
|
+
"checkpoint.pending.cancel": "보호 유지하고 취소",
|
|
1660
|
+
"checkpoint.pending.disable": "보호 해제",
|
|
1661
|
+
"checkpoint.restartRequired": "계속하려면 daemon 세션을 재시작해야 합니다.",
|
|
1662
|
+
"checkpoint.restart": "보호 없이 세션 재시작",
|
|
1663
|
+
"checkpoint.restart.running": "세션을 재시작하는 중…",
|
|
1664
|
+
"checkpoint.error.action": "체크포인트 작업을 완료하지 못했습니다.",
|
|
1665
|
+
"checkpoint.action.restore": "되돌리기",
|
|
1666
|
+
"checkpoint.action.delete": "삭제",
|
|
1667
|
+
"checkpoint.action.skip": "보존",
|
|
1668
|
+
"checkpoint.action.conflict": "충돌",
|
|
1669
|
+
"checkpoint.failure.mutation-failed": "파일 변경 실패",
|
|
1670
|
+
"checkpoint.failure.mutation-outcome-unknown": "결과를 알 수 없어 재확인이 필요함"
|
|
1671
|
+
};
|
|
1672
|
+
const localLoopKo = {
|
|
1673
|
+
"preview.reasonNoProject": "선택된 프로젝트가 없습니다.",
|
|
1674
|
+
"preview.reasonNoPort": "프로젝트에 미리보기 포트가 설정되어 있지 않습니다.",
|
|
1675
|
+
"preview.reasonNoMachine": "프로젝트가 머신에 연결되어 있지 않습니다.",
|
|
1676
|
+
"preview.reasonMachineMissing": "프로젝트의 머신을 찾을 수 없습니다.",
|
|
1677
|
+
"preview.reasonMachineOffline": "{host} 머신이 오프라인입니다.",
|
|
1678
|
+
"preview.reasonWorktreeNotRunning": "worktree 미리보기가 아직 실행 중이 아닙니다.",
|
|
1679
|
+
"preview.reasonWorktreeMachineOffline": "worktree 미리보기 머신이 오프라인입니다.",
|
|
1680
|
+
"preview.standaloneNoPortHint": "로컬 모드에서는 터미널이나 에이전트로 dev 서버를 실행하면 프로젝트 안에서 열린 포트를 감지해 여기에 표시합니다.",
|
|
1681
|
+
"workCompletion.commitNoRemote": "원격 저장소가 없어 push와 PR 생성 없이 커밋까지만 요청합니다.",
|
|
1682
|
+
"workCompletion.commitOnlyAction": "바로 커밋",
|
|
1683
|
+
"projectSettings.workspaceDefaultPlaceholder": "기본 경로: {path}",
|
|
1684
|
+
"preview.standaloneHostNotEmbeddable": "{host} 은(는) 앱 안에 표시할 수 없는 주소입니다. 이 컴퓨터(127.0.0.1)나 .local 호스트만 직접 표시합니다."
|
|
1685
|
+
};
|
|
1686
|
+
const localizationGapKo = {
|
|
1687
|
+
"systemPrompt.loading": "프롬프트를 불러오는 중...",
|
|
1688
|
+
"systemPrompt.presetsAria": "시스템 프롬프트 프리셋",
|
|
1689
|
+
"systemPrompt.instructionsLabel": "지시문",
|
|
1690
|
+
"systemPrompt.placeholder": "데스크탑 세션에 적용할 사용자 지시문",
|
|
1691
|
+
"systemPrompt.charCount": "{count}자",
|
|
1692
|
+
"projectInfo.projectId": "프로젝트 ID",
|
|
1693
|
+
"projectInfo.workspace": "작업 폴더",
|
|
1694
|
+
"projectInfo.devCommand": "개발 서버 명령",
|
|
1695
|
+
"projectInfo.machinePlatform": "머신 플랫폼",
|
|
1696
|
+
"projectInfo.electron": "Electron 버전",
|
|
1697
|
+
"projectInfo.unknown": "알 수 없음",
|
|
1698
|
+
"projectInfo.notConfigured": "설정되지 않음",
|
|
1699
|
+
"projectInfo.unbound": "연결된 머신 없음",
|
|
1700
|
+
"projectMeta.source": "소스",
|
|
1701
|
+
"projectMeta.preview": "미리보기",
|
|
1702
|
+
"projectMeta.machineUnbound": "미지정",
|
|
1703
|
+
"projectMeta.previewUnavailable": "사용 불가",
|
|
1704
|
+
"machineDialog.eyebrow": "세션 대상",
|
|
1705
|
+
"machineDialog.title": "머신 선택",
|
|
1706
|
+
"machineDialog.cancelAria": "머신 선택 취소",
|
|
1707
|
+
"machineDialog.unknownPlatform": "플랫폼 미확인",
|
|
1708
|
+
"newProject.repoInitFailed": "프로젝트는 만들었지만 Git 저장소 초기화에 실패했습니다: {detail}",
|
|
1709
|
+
"standalone.defaultProjectName": "로컬 프로젝트"
|
|
1710
|
+
};
|
|
1711
|
+
const settingsInfoStructureKo = {
|
|
1712
|
+
"about.mode": "모드",
|
|
1713
|
+
"about.mode.demo": "데모 워크스페이스",
|
|
1714
|
+
"about.mode.local": "로컬 워크스페이스",
|
|
1715
|
+
"about.mode.guest": "로컬 워크스페이스 (Guest)",
|
|
1716
|
+
"about.mode.cloud": "Saycode 계정 워크스페이스",
|
|
1717
|
+
"about.appVersion": "앱 버전",
|
|
1718
|
+
"about.userData": "사용자 데이터",
|
|
1719
|
+
"about.unknown": "알 수 없음",
|
|
1720
|
+
"about.updates": "업데이트",
|
|
1721
|
+
"about.versionLine": "현재 {current} · 최신 {latest}",
|
|
1722
|
+
"about.check": "업데이트 확인",
|
|
1723
|
+
"about.download": "업데이트 다운로드",
|
|
1724
|
+
"about.installRestart": "설치 후 재시작",
|
|
1725
|
+
"about.status.checking": "업데이트를 확인하는 중…",
|
|
1726
|
+
"about.status.available": "{version} 버전을 사용할 수 있습니다.",
|
|
1727
|
+
"about.status.downloading": "업데이트 다운로드 중 {percent}%",
|
|
1728
|
+
"about.status.downloaded": "업데이트를 내려받았습니다. 재시작하면 설치됩니다.",
|
|
1729
|
+
"about.status.latest": "최신 버전을 사용 중입니다.",
|
|
1730
|
+
"about.status.unsupported": "업데이트는 패키징된 macOS 빌드에서 제공됩니다.",
|
|
1731
|
+
"about.status.error": "업데이트 확인에 실패했습니다.",
|
|
1732
|
+
"about.status.idle": "업데이트는 자동으로 확인됩니다.",
|
|
1733
|
+
"homeCard.owner": "소유자",
|
|
1734
|
+
"homeCard.branch": "브랜치",
|
|
1735
|
+
"homeCard.updated": "업데이트",
|
|
1736
|
+
"homeCard.preview": "프리뷰",
|
|
1737
|
+
"homeCard.previewAvailable": "사용 가능",
|
|
1738
|
+
"homeCard.previewNotReady": "준비 안 됨",
|
|
1739
|
+
"homeCard.deployed": "배포 시각",
|
|
1740
|
+
"theme.title": "테마",
|
|
1741
|
+
"theme.help": "자동은 macOS 시스템 설정을 따릅니다. 홈 화면 오른쪽 위 토글과 같은 설정입니다.",
|
|
1742
|
+
"theme.auto": "자동",
|
|
1743
|
+
"theme.light": "라이트",
|
|
1744
|
+
"theme.dark": "다크",
|
|
1745
|
+
"theme.autoTitle": "시스템 설정 따르기",
|
|
1746
|
+
"theme.lightTitle": "라이트 모드",
|
|
1747
|
+
"theme.darkTitle": "다크 모드",
|
|
1748
|
+
"standaloneConnectivity.localServer": "로컬 happy-server",
|
|
1749
|
+
"standaloneConnectivity.zrokShare": "zrok 공유 주소",
|
|
1750
|
+
"standaloneConnectivity.zrokFixedName": "zrok 고정 이름",
|
|
1751
|
+
"memoryLayerPanel.remoteNotice": "메모리 레이어 작업은 선택한 프로젝트의 원격 머신에서 실행됩니다.",
|
|
1752
|
+
"memoryLayerPanel.remoteNoticeWithMachine": "메모리 레이어 작업은 선택한 프로젝트의 원격 머신({machine})에서 실행됩니다.",
|
|
1753
|
+
"memoryLayerPanel.targetUnavailable": "원격 머신 대상을 사용할 수 없습니다.",
|
|
1754
|
+
"memoryLayerPanel.checking": "claude-memory-layer를 확인하는 중…",
|
|
1755
|
+
"memoryLayerPanel.installed": "설치됨",
|
|
1756
|
+
"memoryLayerPanel.missing": "미설치",
|
|
1757
|
+
"memoryLayerPanel.updateAvailable": "업데이트 가능",
|
|
1758
|
+
"memoryLayerPanel.version": "버전 {version}",
|
|
1759
|
+
"memoryLayerPanel.versionUnknown": "버전 알 수 없음",
|
|
1760
|
+
"memoryLayerPanel.latest": "최신 {version}",
|
|
1761
|
+
"memoryLayerPanel.notFound": "찾을 수 없음",
|
|
1762
|
+
"memoryLayerPanel.unknown": "알 수 없음",
|
|
1763
|
+
"memoryLayerPanel.notAssigned": "지정되지 않음",
|
|
1764
|
+
"memoryLayerPanel.remoteMachine": "원격 머신",
|
|
1765
|
+
"memoryLayerPanel.installGlobally": "전역 설치",
|
|
1766
|
+
"memoryLayerPanel.updatePackage": "패키지 업데이트",
|
|
1767
|
+
"memoryLayerPanel.reinstallHooks": "훅 다시 설치",
|
|
1768
|
+
"memoryLayerPanel.installing": "설치하는 중…",
|
|
1769
|
+
"memoryLayerPanel.installed.notice": "원격 머신에 claude-memory-layer가 준비됐습니다.",
|
|
1770
|
+
"memoryLayerPanel.agentsTitle": "MCP 에이전트",
|
|
1771
|
+
"memoryLayerPanel.detected": "감지됨",
|
|
1772
|
+
"memoryLayerPanel.registered": "등록됨",
|
|
1773
|
+
"memoryLayerPanel.register": "등록",
|
|
1774
|
+
"memoryLayerPanel.registering": "등록하는 중…",
|
|
1775
|
+
"memoryLayerPanel.registrationUpdated": "{agent} MCP 등록을 원격 머신에 반영했습니다.",
|
|
1776
|
+
"memoryLayerPanel.noAgents": "에이전트 상태를 확인할 수 없습니다.",
|
|
1777
|
+
"memoryLayerPanel.dashboardTitle": "프로젝트 대시보드",
|
|
1778
|
+
"memoryLayerPanel.project": "프로젝트",
|
|
1779
|
+
"memoryLayerPanel.noProject": "선택한 프로젝트 없음",
|
|
1780
|
+
"memoryLayerPanel.path": "경로",
|
|
1781
|
+
"memoryLayerPanel.machine": "머신",
|
|
1782
|
+
"memoryLayerPanel.dashboard": "대시보드",
|
|
1783
|
+
"memoryLayerPanel.remotePort": "원격 포트 {port}",
|
|
1784
|
+
"memoryLayerPanel.notRunning": "실행 중 아님",
|
|
1785
|
+
"memoryLayerPanel.open": "열기",
|
|
1786
|
+
"memoryLayerPanel.stop": "중지",
|
|
1787
|
+
"memoryLayerPanel.starting": "시작하는 중…",
|
|
1788
|
+
"memoryLayerPanel.startDashboard": "대시보드 시작",
|
|
1789
|
+
"memoryLayerPanel.dashboardStarted": "원격 메모리 대시보드를 시작했습니다.",
|
|
1790
|
+
"memoryLayerPanel.dashboardStopped": "원격 메모리 대시보드를 중지했습니다.",
|
|
1791
|
+
"memoryLayerPanel.lastOutput": "마지막 명령 출력",
|
|
1792
|
+
"machineConsole.ownerMe": "나",
|
|
1793
|
+
"overflowMenu.aria": "추가 액션 메뉴",
|
|
1794
|
+
"overflowMenu.title": "추가 액션"
|
|
1795
|
+
};
|
|
1796
|
+
const catalogExtensionsKo = { ...taskNotificationKo, ...machineUsageKo, ...completionInboxKo, ...agentBoardKo, ...fanoutLifecycleKo, ...conversationSearchKo, ...browserCookieImportKo, ...orcaPortKo, ...usageAnalyticsKo, ...outboundWebhookKo, ...scheduledAutomationsKo, ...githubTriggersKo, ...memoryHubKo, ...extensionPanelKo, ...extensionInstallKo, ...personalChatsKo, ...newSessionStarterKo, ...browserConsoleCaptureKo, ...completionSignalKo, ...agentLoopDetectionKo, ...operationModelRetryKo, ...skillCatalogKo, ...ciAutoFixKo, ...collaborationKo, ...backgroundTaskKo, ...sessionTitleKo, ...accountSettingsKo, ...projectScopeKo, ...machineSettingsConsoleKo, ...sessionTokenUsageKo, ...worktreeSetupKo, ...connectionIndicatorKo, ...voiceDictationKo, ...newSessionProfileKo, ...agentBoardBroadcastKo, ...judgementEvidenceKo, ...releaseNoteKo, ...notificationSoundKo, ...officeViewerKo, ...additionalDirectoriesKo, ...agentHandoffKo, ...sidebarUpdateKo, ...autopilotSelfReviewRoundsKo, ...aiCommitPrKo, ...externalWorktreeInboxKo, ...externalSessionVaultKo, ...orgSearchKo, ...terminalFontKo, ...globalPauseKo, ...subagentKo, ...reviewNavigationKo, ...projectLinksKo, ...projectHandoffKo, ...chatFolderUploadKo, ...composerAddMenuKo, ...composerQuickCommandsKo, ...connectorReadinessKo, ...settingsInfoStructureKo, ...localLoopKo, ...localizationGapKo, ...checkpointKo, ...memoryGuardrailsKo, ...memoryEventsKo };
|
|
1408
1797
|
const onboardingKo = {
|
|
1798
|
+
"boot.stage.app": "앱 준비 중…",
|
|
1799
|
+
"boot.stage.localRuntime": "로컬 작업공간 준비 중…",
|
|
1800
|
+
"login.dialogTitle": "Saycode 계정으로 로그인",
|
|
1801
|
+
"login.dialogDescription": "로컬 데이터는 이 컴퓨터에 그대로 남고, 현재 창만 클라우드 작업공간으로 전환됩니다.",
|
|
1802
|
+
"login.dialogClose": "로그인 닫기",
|
|
1803
|
+
"guestRecovery.title": "로컬 작업공간을 준비하지 못했어요",
|
|
1804
|
+
"guestRecovery.body": "Saycode가 로컬 런타임을 시작하지 못했습니다. 다시 시도하거나 계정으로 로그인해 클라우드 작업공간을 사용하세요.",
|
|
1805
|
+
"guestRecovery.retry": "다시 시도",
|
|
1806
|
+
"guestRecovery.retrying": "로컬 작업공간 준비 중…",
|
|
1807
|
+
"account.guest": "게스트",
|
|
1808
|
+
"account.localWorkspace": "로컬 작업공간",
|
|
1409
1809
|
"onboarding.dialogAria": "시작 설정",
|
|
1410
1810
|
"onboarding.eyebrow": "처음 시작하기",
|
|
1411
1811
|
"onboarding.title": "첫 작업까지 함께 준비해요",
|
|
@@ -1428,9 +1828,9 @@ const onboardingKo = {
|
|
|
1428
1828
|
"onboarding.oneCommand.advanced": "고급: 수동 설치",
|
|
1429
1829
|
"onboarding.localMachine.registering": "이 컴퓨터를 등록하는 중…",
|
|
1430
1830
|
"onboarding.localMachine.registered": "✓ 이 컴퓨터가 등록되었습니다. 바로 작업할 수 있어요.",
|
|
1431
|
-
"onboarding.localMachine.askTitle": "
|
|
1432
|
-
"onboarding.localMachine.askBody": "
|
|
1433
|
-
"onboarding.localMachine.askAction": "
|
|
1831
|
+
"onboarding.localMachine.askTitle": "Saycode CLI 설치",
|
|
1832
|
+
"onboarding.localMachine.askBody": "버튼을 누르면 이 컴퓨터의 기존 설정을 안전하게 백업한 뒤 Saycode CLI를 설치하고 연결합니다.",
|
|
1833
|
+
"onboarding.localMachine.askAction": "Saycode CLI 설치",
|
|
1434
1834
|
"onboarding.localMachine.failed": "자동 등록에 실패했습니다. 아래 명령으로 직접 설정할 수 있어요.",
|
|
1435
1835
|
"onboarding.localMachine.retry": "다시 시도",
|
|
1436
1836
|
"onboarding.localMachine.remoteHint": "다른 컴퓨터나 조직 공유 머신은 머신 설정에서 추가할 수 있어요.",
|
|
@@ -1453,7 +1853,101 @@ const onboardingKo = {
|
|
|
1453
1853
|
"onboarding.refresh": "상태 다시 확인",
|
|
1454
1854
|
"onboarding.allDone": "준비가 끝났습니다. 이제 첫 작업을 시작할 수 있어요.",
|
|
1455
1855
|
"onboarding.resume": "시작 설정 이어하기",
|
|
1456
|
-
"onboarding.preflight": "{provider}를 사용하기 전에 로그인을 완료해 주세요."
|
|
1856
|
+
"onboarding.preflight": "{provider}를 사용하기 전에 로그인을 완료해 주세요.",
|
|
1857
|
+
"onboarding.page.happy": "Saycode CLI",
|
|
1858
|
+
"onboarding.page.agents": "AI 도구",
|
|
1859
|
+
"onboarding.page.notifications": "알림",
|
|
1860
|
+
"onboarding.page.happyTitle": "Saycode CLI 준비",
|
|
1861
|
+
"onboarding.page.happyBody": "설치와 버전을 확인하고, 필요한 작업만 이 화면에서 실행합니다.",
|
|
1862
|
+
"onboarding.page.agentsTitle": "Claude Code와 Codex",
|
|
1863
|
+
"onboarding.page.agentsBody": "사용할 AI 도구를 설치하고 계정 로그인을 완료하세요.",
|
|
1864
|
+
"onboarding.page.notificationsTitle": "알림 설정",
|
|
1865
|
+
"onboarding.page.notificationsBody": "응답 요청과 작업 완료를 놓치지 않도록 알림과 사운드를 설정하세요.",
|
|
1866
|
+
"onboarding.page.back": "이전",
|
|
1867
|
+
"onboarding.page.continue": "계속",
|
|
1868
|
+
"onboarding.page.addProject": "첫 프로젝트 추가",
|
|
1869
|
+
"onboarding.page.configured": "설정됨",
|
|
1870
|
+
"onboarding.page.optional": "선택",
|
|
1871
|
+
"onboarding.page.testNotification": "테스트 알림 보내기",
|
|
1872
|
+
"onboarding.page.testNotificationSending": "테스트 알림 보내는 중…",
|
|
1873
|
+
"onboarding.page.testNotificationSuccess": "테스트 알림을 보냈습니다.",
|
|
1874
|
+
"onboarding.page.testNotificationUnsupported": "이 환경에서는 OS 알림을 사용할 수 없습니다.",
|
|
1875
|
+
"onboarding.page.testNotificationFailed": "테스트 알림을 보내지 못했습니다.",
|
|
1876
|
+
"onboarding.page.testNotificationTitle": "Saycode 알림 테스트",
|
|
1877
|
+
"onboarding.page.testNotificationBody": "알림이 잘 도착하고 있어요.",
|
|
1878
|
+
"onboarding.cli.installed": "설치됨",
|
|
1879
|
+
"onboarding.cli.standaloneBody": "Standalone 앱에 포함된 로컬 런타임을 사용합니다.",
|
|
1880
|
+
"onboarding.cli.currentBody": "이 컴퓨터의 Saycode CLI가 최신 버전입니다.",
|
|
1881
|
+
"onboarding.cli.installTitle": "이 컴퓨터에 CLI가 필요합니다",
|
|
1882
|
+
"onboarding.cli.installBody": "Saycode가 설치, 계정 연결, 최종 확인을 순서대로 진행합니다.",
|
|
1883
|
+
"onboarding.cli.installAction": "Saycode CLI 설치",
|
|
1884
|
+
"onboarding.cli.installing": "설치 중…",
|
|
1885
|
+
"onboarding.cli.verifying": "설치 상태 확인 중…",
|
|
1886
|
+
"onboarding.cli.stage.prepare": "설치 준비",
|
|
1887
|
+
"onboarding.cli.stage.install": "CLI 설치 및 연결",
|
|
1888
|
+
"onboarding.cli.stage.verify": "설치 상태 확인",
|
|
1889
|
+
"onboarding.cli.updateTitle": "업데이트가 필요합니다",
|
|
1890
|
+
"onboarding.cli.updateAction": "업데이트 실행",
|
|
1891
|
+
"onboarding.cli.updating": "업데이트 후 설치 상태를 확인하는 중…",
|
|
1892
|
+
"onboarding.cli.checking": "설치 상태 확인 중…",
|
|
1893
|
+
"onboarding.cli.checkFailedTitle": "설치 상태를 확인하지 못했습니다",
|
|
1894
|
+
"onboarding.cli.checkFailedBody": "연결을 확인한 뒤 다시 시도해 주세요.",
|
|
1895
|
+
"onboarding.cli.retry": "다시 확인",
|
|
1896
|
+
"onboarding.cli.nav.checking": "확인 중",
|
|
1897
|
+
"onboarding.cli.nav.install-required": "설치 필요",
|
|
1898
|
+
"onboarding.cli.nav.installing": "설치 중",
|
|
1899
|
+
"onboarding.cli.nav.installed": "설치됨",
|
|
1900
|
+
"onboarding.cli.nav.update-required": "업데이트 필요",
|
|
1901
|
+
"onboarding.cli.nav.updating": "업데이트 중",
|
|
1902
|
+
"onboarding.cli.nav.failed": "확인 필요",
|
|
1903
|
+
"onboarding.provider.checking": "확인 중",
|
|
1904
|
+
"onboarding.provider.connected": "연결됨",
|
|
1905
|
+
"onboarding.provider.install-required": "설치 필요",
|
|
1906
|
+
"onboarding.provider.login-required": "로그인 필요",
|
|
1907
|
+
"onboarding.provider.failed": "확인 실패",
|
|
1908
|
+
"onboarding.provider.needsConnection": "연결 필요",
|
|
1909
|
+
"firstProject.title": "프로젝트 추가",
|
|
1910
|
+
"firstProject.description": "Saycode를 시작하려면 프로젝트를 추가하세요.",
|
|
1911
|
+
"firstProject.close": "프로젝트 추가 창 닫기",
|
|
1912
|
+
"firstProject.host": "호스트",
|
|
1913
|
+
"firstProject.selectHost": "호스트 선택",
|
|
1914
|
+
"firstProject.noHost": "사용 가능한 머신 없음",
|
|
1915
|
+
"firstProject.thisComputer": "이 컴퓨터",
|
|
1916
|
+
"firstProject.remoteHost": "원격 머신",
|
|
1917
|
+
"firstProject.online": "온라인",
|
|
1918
|
+
"firstProject.offline": "오프라인",
|
|
1919
|
+
"firstProject.addMachine": "다른 머신 추가",
|
|
1920
|
+
"firstProject.addMachineHint": "원격 머신 또는 공유 머신 연결",
|
|
1921
|
+
"firstProject.startMethod": "프로젝트 시작 방법",
|
|
1922
|
+
"firstProject.otherWays": "프로젝트를 추가하는 다른 방법",
|
|
1923
|
+
"firstProject.source.existing-folder.title": "기존 폴더 열기",
|
|
1924
|
+
"firstProject.source.existing-folder.description": "이 컴퓨터나 선택한 머신의 폴더를 사용합니다.",
|
|
1925
|
+
"firstProject.source.zip.title": "ZIP으로 가져오기",
|
|
1926
|
+
"firstProject.source.zip.description": "압축 파일에서 로컬 프로젝트를 만듭니다.",
|
|
1927
|
+
"firstProject.source.git.title": "Git URL에서 복제",
|
|
1928
|
+
"firstProject.source.git.description": "원격 Git 저장소를 선택한 머신에 복제합니다.",
|
|
1929
|
+
"firstProject.source.blank.title": "새 프로젝트 만들기",
|
|
1930
|
+
"firstProject.source.blank.description": "빈 폴더에서 새 프로젝트를 시작합니다.",
|
|
1931
|
+
"firstProject.form.back": "이전",
|
|
1932
|
+
"firstProject.form.machine": "프로젝트를 만들 머신",
|
|
1933
|
+
"firstProject.form.blank.title": "새 프로젝트",
|
|
1934
|
+
"firstProject.form.blank.description": "이름을 정하면 로컬에 새 프로젝트를 만듭니다.",
|
|
1935
|
+
"firstProject.form.git.title": "코드 저장소 가져오기",
|
|
1936
|
+
"firstProject.form.git.description": "Git 주소를 입력하면 이 컴퓨터에 저장소를 복제합니다.",
|
|
1937
|
+
"firstProject.form.existing-folder.title": "기존 폴더 사용",
|
|
1938
|
+
"firstProject.form.existing-folder.description": "로컬 폴더를 Saycode 프로젝트로 연결합니다.",
|
|
1939
|
+
"firstProject.form.zip.title": "ZIP으로 가져오기",
|
|
1940
|
+
"firstProject.form.zip.description": "압축 파일을 선택하면 프로젝트 이름을 자동으로 정합니다.",
|
|
1941
|
+
"firstProject.form.zip.pick": "ZIP 파일 선택",
|
|
1942
|
+
"firstProject.form.zip.drop": "ZIP 파일을 선택하거나 여기로 드래그하세요",
|
|
1943
|
+
"guestRecovery.detailsSummary": "진단 정보 보기",
|
|
1944
|
+
"onboarding.cli.globalInstallTitle": "터미널에서도 Saycode CLI 사용하기",
|
|
1945
|
+
"onboarding.cli.globalInstallBody": "선택 사항입니다. npm으로 전역 happy 명령을 설치합니다: npm i -g @buzzni/happy-cli",
|
|
1946
|
+
"onboarding.cli.globalInstallAction": "전역 CLI 설치",
|
|
1947
|
+
"onboarding.cli.globalInstalling": "설치 중… 몇 분 걸릴 수 있습니다",
|
|
1948
|
+
"onboarding.cli.globalInstalled": "전역 CLI 설치됨 ({version})",
|
|
1949
|
+
"onboarding.cli.globalInstallFailed": "전역 CLI 설치 실패: {detail}",
|
|
1950
|
+
"onboarding.cli.globalInstalledUnversioned": "전역 CLI 설치됨"
|
|
1457
1951
|
};
|
|
1458
1952
|
const standaloneRemoteOnboardingKo = {
|
|
1459
1953
|
"onboarding.optional": " (선택)",
|
|
@@ -1500,6 +1994,44 @@ const ko = {
|
|
|
1500
1994
|
"common.reset": "초기화",
|
|
1501
1995
|
"common.cancel": "취소",
|
|
1502
1996
|
"common.close": "닫기",
|
|
1997
|
+
"networkDataSaver.transferConfirm": "{operation} 작업은 데이터를 많이 사용할 수 있습니다. 예상 크기: {size}. 계속할까요?",
|
|
1998
|
+
"networkDataSaver.sizeUnknown": "알 수 없음",
|
|
1999
|
+
"networkDataSaver.operation.update-download": "업데이트 다운로드",
|
|
2000
|
+
"networkDataSaver.operation.file-upload": "파일 업로드",
|
|
2001
|
+
"networkDataSaver.operation.folder-upload": "폴더 업로드",
|
|
2002
|
+
"networkDataSaver.operation.file-download": "파일 다운로드",
|
|
2003
|
+
"networkDataSaver.operation.project-deploy": "프로젝트 배포",
|
|
2004
|
+
"networkDataSaver.operation.project-import": "프로젝트 가져오기",
|
|
2005
|
+
"networkDataSaver.title": "네트워크 데이터 절약",
|
|
2006
|
+
"networkDataSaver.description": "macOS가 비용이 들거나 저데이터 모드인 경로로 판단하면 백그라운드 데이터 사용을 자동으로 줄입니다.",
|
|
2007
|
+
"networkDataSaver.loading": "네트워크 상태를 확인하는 중…",
|
|
2008
|
+
"networkDataSaver.unsupported": "이 앱 버전에서는 네트워크 비용 상태를 확인할 수 없습니다.",
|
|
2009
|
+
"networkDataSaver.status.active": "데이터 절약 중",
|
|
2010
|
+
"networkDataSaver.status.inactive": "데이터 절약 꺼짐",
|
|
2011
|
+
"networkDataSaver.modeLabel": "절약 방식",
|
|
2012
|
+
"networkDataSaver.mode.automatic": "자동",
|
|
2013
|
+
"networkDataSaver.mode.always-save": "항상 절약",
|
|
2014
|
+
"networkDataSaver.mode.never-save": "절약 안 함",
|
|
2015
|
+
"networkDataSaver.interfaceLabel": "현재 연결",
|
|
2016
|
+
"networkDataSaver.interface.wifi": "Wi-Fi",
|
|
2017
|
+
"networkDataSaver.interface.wired-ethernet": "유선 이더넷",
|
|
2018
|
+
"networkDataSaver.interface.cellular": "셀룰러",
|
|
2019
|
+
"networkDataSaver.interface.loopback": "로컬 연결",
|
|
2020
|
+
"networkDataSaver.interface.other": "기타 연결",
|
|
2021
|
+
"networkDataSaver.interface.unknown": "알 수 없음",
|
|
2022
|
+
"networkDataSaver.reasonLabel": "판단 이유",
|
|
2023
|
+
"networkDataSaver.reason.expensive": "비용이 드는 네트워크로 감지됨",
|
|
2024
|
+
"networkDataSaver.reason.constrained": "저데이터 모드로 감지됨",
|
|
2025
|
+
"networkDataSaver.reason.offline": "오프라인",
|
|
2026
|
+
"networkDataSaver.reason.unrestricted": "일반 네트워크로 감지됨",
|
|
2027
|
+
"networkDataSaver.reason.unknown": "네트워크 비용 상태를 알 수 없음",
|
|
2028
|
+
"networkDataSaver.reason.user-always": "사용자가 항상 절약을 선택함",
|
|
2029
|
+
"networkDataSaver.reason.user-never": "사용자가 절약 안 함을 선택함",
|
|
2030
|
+
"networkDataSaver.deferred.title": "절약 중 지연되는 작업",
|
|
2031
|
+
"networkDataSaver.activeHelp": "수동 새로고침은 계속 사용할 수 있고, 대용량 작업은 확인 후 한 번 허용할 수 있습니다.",
|
|
2032
|
+
"networkDataSaver.deferred.model": "난이도 모델 최초 다운로드",
|
|
2033
|
+
"networkDataSaver.deferred.memory": "Memory Layer 대화 백필",
|
|
2034
|
+
"networkDataSaver.deferred.gitPr": "백그라운드 Git 및 PR 새로고침",
|
|
1503
2035
|
"happyCliUpdate.title": "Happy CLI 업데이트가 있습니다",
|
|
1504
2036
|
"happyCliUpdate.checkTitle": "Happy CLI 업데이트 확인",
|
|
1505
2037
|
"happyCliUpdate.managerTitle": "Happy CLI 업데이트 관리",
|
|
@@ -1526,7 +2058,7 @@ const ko = {
|
|
|
1526
2058
|
"happyCliUpdate.cancel": "취소",
|
|
1527
2059
|
// Boot screen
|
|
1528
2060
|
"boot.preparing": "Saycode 준비 중...",
|
|
1529
|
-
"boot.stage.credentials": "
|
|
2061
|
+
"boot.stage.credentials": "로그인 상태 확인 중…",
|
|
1530
2062
|
"boot.stage.workspace": "워크스페이스 불러오는 중…",
|
|
1531
2063
|
// Language selection (first run)
|
|
1532
2064
|
"language.select.title": "언어를 선택하세요",
|
|
@@ -1591,8 +2123,8 @@ const ko = {
|
|
|
1591
2123
|
"zipImport.transferFailed": "{count}개 파일을 전송하지 못했습니다. ({path}: {reason})",
|
|
1592
2124
|
"zipImport.machineInfoMissing": "머신 정보(homeDir/workspaceRoot)가 부족해 가져올 위치를 결정할 수 없습니다.",
|
|
1593
2125
|
"zipImport.emptyArchive": "압축 파일 안에 가져올 파일이 없습니다.",
|
|
1594
|
-
"sidebarPanel.showAllChats": "
|
|
1595
|
-
"sidebarPanel.showAllChatsAria": "{project}
|
|
2126
|
+
"sidebarPanel.showAllChats": "대화 더보기 ({count}개 남음)",
|
|
2127
|
+
"sidebarPanel.showAllChatsAria": "{project}에서 대화 더 보기",
|
|
1596
2128
|
"sidebarPanel.newChatAria": "{name} 프로젝트에서 새 대화 시작",
|
|
1597
2129
|
"home.eyebrow": "워크스페이스 홈",
|
|
1598
2130
|
"home.newProject": "새 프로젝트",
|
|
@@ -1647,7 +2179,6 @@ const ko = {
|
|
|
1647
2179
|
"newProject.gitNeedUrl": "Git 저장소 주소를 입력해 주세요.",
|
|
1648
2180
|
"newProject.importDone": "'{name}' 프로젝트에 파일 {count}개를 가져왔습니다.",
|
|
1649
2181
|
"newProject.importFailed": "'{name}' 프로젝트는 생성했지만 파일을 가져오지 못했습니다. {detail}",
|
|
1650
|
-
"composer.attachFile": "파일 첨부",
|
|
1651
2182
|
"composer.attachNeedMachine": "첨부하려면 머신 연결이 필요합니다",
|
|
1652
2183
|
"composer.attachNoMachine": "파일을 업로드할 머신을 찾을 수 없습니다.",
|
|
1653
2184
|
"composer.attachUploadFailed": "파일을 업로드하지 못했습니다.",
|
|
@@ -1674,7 +2205,7 @@ const ko = {
|
|
|
1674
2205
|
"workspace.orgAdminMenuEntry": "조직 관리",
|
|
1675
2206
|
// Org admin console (specs/desktop-org-admin-console)
|
|
1676
2207
|
"orgAdmin.title": "조직 관리",
|
|
1677
|
-
"orgAdmin.eyebrow": "
|
|
2208
|
+
"orgAdmin.eyebrow": "조직",
|
|
1678
2209
|
"orgAdmin.close": "닫기",
|
|
1679
2210
|
"orgAdmin.tab.members": "멤버 관리",
|
|
1680
2211
|
"orgAdmin.tab.settings": "조직 설정",
|
|
@@ -1703,7 +2234,7 @@ const ko = {
|
|
|
1703
2234
|
"orgAdmin.settingsSaved": "조직 설정이 저장되었습니다.",
|
|
1704
2235
|
"orgAdmin.tab.audit": "감사 로그",
|
|
1705
2236
|
"orgAdmin.tab.machineMonitor": "머신 모니터링",
|
|
1706
|
-
"orgAdmin.tab.skill": "
|
|
2237
|
+
"orgAdmin.tab.skill": "스킬",
|
|
1707
2238
|
"orgAdmin.tab.projects": "프로젝트",
|
|
1708
2239
|
"orgAdmin.skillSearch": "Skill 검색",
|
|
1709
2240
|
"orgAdmin.skillSearchPlaceholder": "Skill 또는 설명 검색...",
|
|
@@ -1818,7 +2349,7 @@ const ko = {
|
|
|
1818
2349
|
"orgAdmin.githubOwnerPlaceholder": "예: myteam",
|
|
1819
2350
|
"orgAdmin.label": "레이블 (선택)",
|
|
1820
2351
|
"orgAdmin.labelPlaceholder": "Main Account, Backup Account 등",
|
|
1821
|
-
"orgAdmin.githubPat": "
|
|
2352
|
+
"orgAdmin.githubPat": "개인 액세스 토큰",
|
|
1822
2353
|
"orgAdmin.githubPatRequired": "PAT를 입력하세요.",
|
|
1823
2354
|
"orgAdmin.githubPatPlaceholder": "github_pat_... 형식의 PAT",
|
|
1824
2355
|
"orgAdmin.githubPatUnchanged": "변경 없음 (기존 PAT 유지)",
|
|
@@ -1941,7 +2472,7 @@ const ko = {
|
|
|
1941
2472
|
// Super admin console (specs/desktop-org-admin-console)
|
|
1942
2473
|
"systemAdmin.menuEntry": "슈퍼 관리자",
|
|
1943
2474
|
"systemAdmin.title": "슈퍼 관리자",
|
|
1944
|
-
"systemAdmin.eyebrow": "
|
|
2475
|
+
"systemAdmin.eyebrow": "시스템",
|
|
1945
2476
|
"systemAdmin.companiesTitle": "전체 조직",
|
|
1946
2477
|
"systemAdmin.createCompany": "조직 생성",
|
|
1947
2478
|
"systemAdmin.create": "생성",
|
|
@@ -2016,7 +2547,7 @@ const ko = {
|
|
|
2016
2547
|
"panel.recovery.loginDesc": "로그인 후 터미널을 시작할 수 있습니다.",
|
|
2017
2548
|
// Chat composer
|
|
2018
2549
|
"composer.messageAria": "메시지",
|
|
2019
|
-
"composer.placeholder": "
|
|
2550
|
+
"composer.placeholder": "메시지를 입력하세요",
|
|
2020
2551
|
"composer.promptSuggestionPaused": "{window} 사용량 {percent}% — 사용량 경고 중에는 Claude가 추천 질문을 만들지 않습니다",
|
|
2021
2552
|
"composer.promptSuggestionPausedReset": "{reset} 초기화",
|
|
2022
2553
|
"composer.starting": "시작 중...",
|
|
@@ -2069,6 +2600,7 @@ const ko = {
|
|
|
2069
2600
|
"sessionWorktree.branch": "브랜치: {branch}",
|
|
2070
2601
|
"sessionWorktree.path": "경로: {path}",
|
|
2071
2602
|
"sessionWorktree.merged": "병합됨",
|
|
2603
|
+
"chatPullRequest.open": "{repository} PR #{number} 열기",
|
|
2072
2604
|
// Project git controls
|
|
2073
2605
|
"gitControls.branchLoading": "Git branch 확인 중",
|
|
2074
2606
|
"gitControls.noRemote": "origin remote 또는 branch를 찾을 수 없습니다.",
|
|
@@ -2078,9 +2610,9 @@ const ko = {
|
|
|
2078
2610
|
"gitControls.manageBranchTitle": "Branch 변경 및 생성",
|
|
2079
2611
|
"gitControls.manageBranchTitleWithDesc": "Branch 변경 및 생성 · {description}",
|
|
2080
2612
|
"gitControls.pullAria": "{branch} 브랜치 pull",
|
|
2081
|
-
"gitControls.pullTitle": "{branch} pull",
|
|
2613
|
+
"gitControls.pullTitle": "{branch} 가져오기(pull)",
|
|
2082
2614
|
"gitControls.pushAria": "{branch} 브랜치 push",
|
|
2083
|
-
"gitControls.pushTitle": "{branch} push",
|
|
2615
|
+
"gitControls.pushTitle": "{branch} 올리기(push)",
|
|
2084
2616
|
"gitControls.manageWorktreeAria": "{name} worktree 관리",
|
|
2085
2617
|
"gitControls.manageWorktreeTitle": "Worktree 관리",
|
|
2086
2618
|
"gitControls.operationResultDialogTitle": "{label} 결과",
|
|
@@ -2525,6 +3057,12 @@ const ko = {
|
|
|
2525
3057
|
// Owner workspace sync (team share) messages
|
|
2526
3058
|
"ownerSync.hint.credentialsUnavailable": "원격 머신 / 인증 정보가 없어요. 프로젝트 메뉴에서 머신을 한 번 연결한 뒤 다시 시도해 주세요.",
|
|
2527
3059
|
"ownerSync.successConnectionUpdated": "저장소 연결을 변경했습니다.",
|
|
3060
|
+
"ownerSync.deferred.workspaceDirNotFound": "저장소 연결은 완료됐지만 워크스페이스 폴더를 찾지 못해 코드는 아직 저장하지 않았어요. 프로젝트를 연 뒤 다시 저장해 주세요.",
|
|
3061
|
+
"ownerSync.deferred.workspaceEmpty": "저장소 연결은 완료됐지만 저장할 파일이 없어 코드는 아직 저장하지 않았어요. 파일을 만든 뒤 다시 저장해 주세요.",
|
|
3062
|
+
"ownerSync.deferred.credentialsUnavailable": "저장소 연결은 완료됐지만 머신 자격 증명을 사용할 수 없어 코드는 아직 저장하지 않았어요. 자격 증명을 확인한 뒤 다시 저장해 주세요.",
|
|
3063
|
+
"ownerSync.deferred.machineHomeUnresolvable": "저장소 연결은 완료됐지만 머신 홈 폴더를 확인할 수 없어 코드는 아직 저장하지 않았어요. 머신 홈 폴더를 확인한 뒤 다시 저장해 주세요.",
|
|
3064
|
+
"ownerSync.deferred.machineUnavailable": "저장소 연결은 완료됐지만 머신이 오프라인이라 코드는 아직 저장하지 않았어요. 머신을 다시 연결한 뒤 저장해 주세요.",
|
|
3065
|
+
"ownerSync.deferred.default": "저장소 연결은 완료됐지만 코드는 아직 저장하지 않았어요. 프로젝트와 머신 상태를 확인한 뒤 다시 저장해 주세요.",
|
|
2528
3066
|
"ownerSync.hint.machineHomeUnresolvable": "원격 머신을 찾을 수 없어요. happy daemon 이 실행 중인지 확인해 주세요.",
|
|
2529
3067
|
"ownerSync.hint.machineUnavailable": "원격 머신 daemon 이 오프라인이에요. happy daemon 을 시작한 뒤 다시 시도해 주세요.",
|
|
2530
3068
|
"ownerSync.hint.missingTools": "원격 머신에 git / tar 가 설치되어 있지 않아요. 운영자에게 설치 요청을 보내주세요.",
|
|
@@ -2787,6 +3325,22 @@ const ko = {
|
|
|
2787
3325
|
"community.cloneFailed": "커뮤니티 프로젝트를 받아오지 못했습니다.",
|
|
2788
3326
|
"login.credentialsRequired": "이메일과 비밀번호를 입력해주세요.",
|
|
2789
3327
|
"login.loginFailed": "로그인에 실패했습니다.",
|
|
3328
|
+
"login.mfaTitle": "추가 인증",
|
|
3329
|
+
"login.mfaLead": "이 계정은 2단계 인증이 필요합니다. 인증 앱의 6자리 코드를 입력해주세요.",
|
|
3330
|
+
"login.mfaMethodsLead": "이 계정은 2단계 인증이 필요합니다. 패스키 또는 등록한 인증 앱으로 인증해주세요.",
|
|
3331
|
+
"login.mfaPasskey": "패스키로 인증",
|
|
3332
|
+
"login.mfaPasskeyRetry": "패스키 인증 다시 열기",
|
|
3333
|
+
"login.mfaPasskeyWaiting": "시스템 브라우저에서 iPhone 또는 이 Mac의 패스키 인증을 완료해주세요.",
|
|
3334
|
+
"login.mfaTotpFallback": "브라우저를 닫아도 인증 앱 코드로 계속할 수 있습니다.",
|
|
3335
|
+
"login.mfaCodeLabel": "인증 코드",
|
|
3336
|
+
"login.mfaCodePlaceholder": "6자리 숫자",
|
|
3337
|
+
"login.mfaSubmit": "인증하고 로그인",
|
|
3338
|
+
"login.mfaCancel": "다른 계정으로 로그인",
|
|
3339
|
+
"login.mfaInvalidCode": "인증 코드가 올바르지 않습니다. 다시 시도해주세요.",
|
|
3340
|
+
"login.mfaExpired": "인증 시간이 만료되었습니다. 다시 로그인해주세요.",
|
|
3341
|
+
"login.mfaRateLimited": "시도가 너무 많습니다. 잠시 후 다시 시도해주세요.",
|
|
3342
|
+
"login.mfaUnsupported": "이 계정에서 사용할 수 있는 2단계 인증 방법이 없습니다. 웹에서 인증 앱 또는 패스키를 등록한 뒤 다시 시도해주세요.",
|
|
3343
|
+
"login.mfaPasskeyFailed": "패스키 인증을 시작하지 못했습니다. 다시 시도하거나 인증 앱 코드를 사용해주세요.",
|
|
2790
3344
|
"standalone.startFailed": "로컬 standalone 서버를 시작하지 못했습니다.",
|
|
2791
3345
|
"workspace.loadFailed": "워크스페이스를 불러오지 못했습니다.",
|
|
2792
3346
|
"standalone.modeStartFailed": "로컬 standalone 모드를 시작하지 못했습니다.",
|
|
@@ -2974,6 +3528,12 @@ const ko = {
|
|
|
2974
3528
|
"worktreeOp.merging": "머지 중...",
|
|
2975
3529
|
"worktreeOp.mergeFailed": "머지에 실패했습니다.",
|
|
2976
3530
|
"worktreeOp.mergeDone": "머지 완료. 이제 worktree를 정리할 수 있습니다.",
|
|
3531
|
+
"worktreeOp.demoAutoMergeBlocked": "데모 모드에서는 auto-merge를 사용할 수 없습니다.",
|
|
3532
|
+
"worktreeOp.autoMergeEnabling": "auto-merge 켜는 중...",
|
|
3533
|
+
"worktreeOp.autoMergeDisabling": "auto-merge 끄는 중...",
|
|
3534
|
+
"worktreeOp.autoMergeEnabled": "auto-merge가 켜졌습니다. 머지 조건(체크·필수 리뷰 등)을 모두 충족하면 자동으로 머지됩니다.",
|
|
3535
|
+
"worktreeOp.autoMergeDisabled": "auto-merge가 꺼졌습니다.",
|
|
3536
|
+
"worktreeOp.autoMergeFailed": "auto-merge 설정을 바꾸지 못했습니다.",
|
|
2977
3537
|
"worktreeOp.checkingStatus": "worktree 상태 확인 중...",
|
|
2978
3538
|
"worktreeOp.forceDeleteUnpushed": "worktree에 푸시되지 않은 커밋 {count}개가 있습니다. 강제로 삭제할까요? 커밋이 사라집니다.",
|
|
2979
3539
|
"worktreeOp.forceDeleteDirty": "worktree에 정리되지 않은 변경이 있습니다. 강제로 삭제할까요? 변경 사항이 사라집니다.",
|
|
@@ -3397,6 +3957,7 @@ const ko = {
|
|
|
3397
3957
|
"file.readFailed": "파일을 읽지 못했습니다.",
|
|
3398
3958
|
"session.loadMessagesFailed": "대화 메시지를 불러올 수 없습니다.",
|
|
3399
3959
|
"session.sendToSessionFailed": "대화에 메시지를 보낼 수 없습니다.",
|
|
3960
|
+
"session.sendBlockedAccountKeyLocked": "이 대화는 계정 키로 잠겨 있습니다. 잠금을 해제한 뒤 다시 시도하세요.",
|
|
3400
3961
|
"message.noEncryptKey": "메시지 삭제 이벤트를 암호화할 세션 키를 찾을 수 없습니다.",
|
|
3401
3962
|
"machineSel.boundMissing": "프로젝트에 연결된 머신 정보를 찾을 수 없습니다. 새로고침 후 다시 시도해주세요.",
|
|
3402
3963
|
"machineSel.connectedMachine": "연결된 머신",
|
|
@@ -3594,6 +4155,8 @@ const ko = {
|
|
|
3594
4155
|
// ProjectDocumentViewer
|
|
3595
4156
|
"inlinePreview.openInViewer": "파일 뷰어로 열기",
|
|
3596
4157
|
"inlinePreview.loading": "미리보기를 불러오는 중",
|
|
4158
|
+
"inlinePreview.loadFailed": "미리보기를 불러오지 못했습니다.",
|
|
4159
|
+
"inlinePreview.retry": "다시 시도",
|
|
3597
4160
|
"documentViewer.docxConvertFailed": "DOCX 문서를 변환하지 못했습니다.",
|
|
3598
4161
|
"documentViewer.pdfViewer": "{name} PDF 뷰어",
|
|
3599
4162
|
"documentViewer.pdfPreparing": "PDF를 준비하는 중...",
|
|
@@ -3644,7 +4207,7 @@ const ko = {
|
|
|
3644
4207
|
"connectors.browserHint": "브라우저에서 승인을 마친 뒤 앱으로 돌아오세요.",
|
|
3645
4208
|
"connectors.availableNextTurn": "연결됐습니다. 다음 메시지부터 사용할 수 있습니다.",
|
|
3646
4209
|
"connectors.readOnlyNote": "읽기 전용으로만 연결합니다.",
|
|
3647
|
-
"connectors.apiKeyLabel": "KNOI API
|
|
4210
|
+
"connectors.apiKeyLabel": "KNOI API 키",
|
|
3648
4211
|
"connectors.apiKeyEnter": "API Key 입력",
|
|
3649
4212
|
"connectors.apiKeyReplace": "API Key 교체",
|
|
3650
4213
|
"connectors.apiKeySave": "API Key 저장",
|
|
@@ -3657,7 +4220,7 @@ const ko = {
|
|
|
3657
4220
|
"remoteFile.saveFailed": "파일을 저장하지 못했습니다.",
|
|
3658
4221
|
"fileDownload.invalidData": "다운로드할 파일 데이터가 올바르지 않습니다.",
|
|
3659
4222
|
"fileDownload.tooLarge": "다운로드 파일은 100 MiB 이하만 지원합니다.",
|
|
3660
|
-
"codex.needsLatestCli": "
|
|
4223
|
+
"codex.needsLatestCli": "선택한 모델을 사용하려면 최신 Codex CLI가 필요합니다. `npm install -g @openai/codex@latest`로 업데이트한 뒤 새 대화를 시작해 주세요.",
|
|
3661
4224
|
"codex.error": "Codex 오류: {detail}",
|
|
3662
4225
|
// specs/desktop-error-surface — 실패 알림의 계층 배지와 복구 액션
|
|
3663
4226
|
"errorSurface.layer.provider": "Provider 오류",
|
|
@@ -3784,20 +4347,24 @@ const ko = {
|
|
|
3784
4347
|
"settingsTab.language": "언어",
|
|
3785
4348
|
"settingsTab.machines": "머신",
|
|
3786
4349
|
"settingsTab.mobile": "모바일 연결",
|
|
4350
|
+
"settingsTab.network": "네트워크 데이터 절약",
|
|
3787
4351
|
"settingsTab.env": "환경 변수",
|
|
3788
4352
|
"settingsTab.security": "보안",
|
|
3789
|
-
"settingsTab.memory": "
|
|
4353
|
+
"settingsTab.memory": "메모리",
|
|
3790
4354
|
"settingsTab.connectors": "커넥터",
|
|
3791
4355
|
"settingsTab.myAiCredentials": "내 AI 계정",
|
|
3792
4356
|
"settingsTab.extensions": "확장",
|
|
3793
4357
|
"settingsTab.about": "정보",
|
|
3794
4358
|
"settingsTab.shortcuts": "단축키",
|
|
3795
|
-
"extensions.help": "필요한 기능만 로컬 확장 패키지로 설치하고 관리합니다.",
|
|
4359
|
+
"extensions.help": "필요한 기능만 공식 또는 로컬 확장 패키지로 설치하고 관리합니다.",
|
|
3796
4360
|
"extensions.installLocal": "로컬 파일 설치",
|
|
3797
4361
|
"extensions.loading": "설치된 확장을 불러오는 중입니다.",
|
|
3798
4362
|
"extensions.empty": "설치된 확장이 없습니다.",
|
|
3799
4363
|
"extensions.permissions": "권한",
|
|
3800
4364
|
"extensions.noPermissions": "요청 권한 없음",
|
|
4365
|
+
"extensions.permission.browserViewerOpen": "접근 가능한 온라인 머신에서 원격 Chrome 화면을 준비해 기본 브라우저로 엽니다. 소프트웨어 설치는 포함하지 않습니다.",
|
|
4366
|
+
"extensions.permission.browserViewerInstall": "선택한 머신에 원격 화면용 시스템 패키지(Xvfb/noVNC)를 설치합니다. Chrome 설치는 포함하지 않습니다.",
|
|
4367
|
+
"extensions.permission.browserViewerInstallChrome": "선택한 머신에 Google Chrome을 설치합니다. 머신의 시스템 소프트웨어가 변경됩니다.",
|
|
3801
4368
|
"extensions.approvePermission": "'{permission}' 승인",
|
|
3802
4369
|
"extensions.revokePermission": "'{permission}' 철회",
|
|
3803
4370
|
"extensions.enable": "활성화",
|
|
@@ -3816,7 +4383,9 @@ const ko = {
|
|
|
3816
4383
|
"extensions.developmentSource": "개발 빌드: {directory}",
|
|
3817
4384
|
"extensions.apiDeprecated": "업데이트 필요",
|
|
3818
4385
|
"extensions.apiDeprecatedHint": "이전 확장 API({apiVersion})로 동작 중이며, 다음 업데이트에서 지원이 끝납니다.",
|
|
3819
|
-
"extensions.
|
|
4386
|
+
"extensions.enablement.enabled": "활성화됨",
|
|
4387
|
+
"extensions.enablement.disabled": "비활성화됨",
|
|
4388
|
+
"extensions.runtime.inactive": "대기 중",
|
|
3820
4389
|
"extensions.runtime.active": "실행 중",
|
|
3821
4390
|
"extensions.runtime.quarantined": "격리됨",
|
|
3822
4391
|
"shortcuts.title": "단축키",
|
|
@@ -3909,7 +4478,7 @@ const ko = {
|
|
|
3909
4478
|
"review.self.disabledWaiting": "먼저 대기 중인 질문이나 권한 요청에 응답해주세요.",
|
|
3910
4479
|
"review.self.rounds": "반복 횟수",
|
|
3911
4480
|
"review.self.roundsHint": "medium 이상 지적이 나오면 남은 횟수만큼 리뷰를 자동으로 이어갑니다. low·nit만 남으면 바로 끝냅니다.",
|
|
3912
|
-
"review.self.appOpenHint": "
|
|
4481
|
+
"review.self.appOpenHint": "상시 실행 에이전트가 최신 버전이면 앱을 완전히 종료해도 계속됩니다. 지원하지 않는 환경에서는 앱이 켜져 있어야 합니다.",
|
|
3913
4482
|
"review.self.loopActive": "반복 리뷰 {round}/{total} 진행 중",
|
|
3914
4483
|
"review.self.loopStop": "반복 중지",
|
|
3915
4484
|
"review.self.loopDismiss": "기록 지우기",
|
|
@@ -3920,7 +4489,7 @@ const ko = {
|
|
|
3920
4489
|
"review.self.loopReason.roundsExhausted": "지정한 횟수를 모두 사용",
|
|
3921
4490
|
"review.self.loopReason.unstructured": "결과를 판정할 수 없어 중단",
|
|
3922
4491
|
"review.self.loopReason.sendFailed": "다음 라운드를 보내지 못함",
|
|
3923
|
-
"review.self.loopReason.cancelled": "
|
|
4492
|
+
"review.self.loopReason.cancelled": "사용자 요청으로 중단",
|
|
3924
4493
|
"review.history.title": "독립 리뷰 기록",
|
|
3925
4494
|
"review.storage.writeFailed": "이 기기에 리뷰 기록을 저장하지 못했습니다. 앱을 다시 켜면 이 목록이 비어 있을 수 있습니다.",
|
|
3926
4495
|
"review.recovery.title": "기록 복구",
|
|
@@ -3980,7 +4549,7 @@ const ko = {
|
|
|
3980
4549
|
"review.error.sourceProjectMissing": "리뷰 원본 프로젝트를 찾을 수 없습니다.",
|
|
3981
4550
|
"review.error.sourceSessionMissing": "리뷰 원본 대화를 찾을 수 없습니다.",
|
|
3982
4551
|
// Project processes (specs/desktop-process-ownership R4)
|
|
3983
|
-
"projectProcesses.eyebrow": "
|
|
4552
|
+
"projectProcesses.eyebrow": "실행 중인 프로세스",
|
|
3984
4553
|
"projectProcesses.dialogAria": "{name} 프로세스 보기",
|
|
3985
4554
|
"projectProcesses.closeAria": "프로세스 보기 닫기",
|
|
3986
4555
|
"projectProcesses.listAria": "{name}의 실행 중인 프로세스 목록",
|
|
@@ -4158,6 +4727,7 @@ function interruptDecision(state) {
|
|
|
4158
4727
|
return state.phase === "idle" ? "exit-cli" : "open-menu";
|
|
4159
4728
|
}
|
|
4160
4729
|
const AGENT_UNTIL_VALUES = ["turn-end", "waiting-input", "completed"];
|
|
4730
|
+
const STATE_WINDOW_MESSAGES = 50;
|
|
4161
4731
|
const DEFAULT_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4162
4732
|
const MAX_WAIT_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
4163
4733
|
function isAgentUntil(value2) {
|
|
@@ -4358,6 +4928,12 @@ function collectFlags(argv, startIndex) {
|
|
|
4358
4928
|
flags.last = takeNumber(argv, index, token);
|
|
4359
4929
|
index += 1;
|
|
4360
4930
|
break;
|
|
4931
|
+
case "--status":
|
|
4932
|
+
flags.status = true;
|
|
4933
|
+
break;
|
|
4934
|
+
case "--any":
|
|
4935
|
+
flags.any = true;
|
|
4936
|
+
break;
|
|
4361
4937
|
case "--wait":
|
|
4362
4938
|
flags.wait = true;
|
|
4363
4939
|
break;
|
|
@@ -4513,12 +5089,13 @@ Options
|
|
|
4513
5089
|
|
|
4514
5090
|
Agent commands (machine-readable JSON on stdout, exit 3 on failure)
|
|
4515
5091
|
agent whoami Show this session's contact point and limits
|
|
4516
|
-
agent ls
|
|
4517
|
-
agent read <session> [--last n]
|
|
5092
|
+
agent ls [--status] List sessions in scope; --status adds state/pending/cursor
|
|
5093
|
+
agent read <session> [--last n] [--since seq] Recent messages + completion signal
|
|
4518
5094
|
agent wait <session> --until <state> turn-end | waiting-input | completed
|
|
5095
|
+
agent wait --any <s1> <s2>... --until <state> Block until any of them holds it
|
|
4519
5096
|
agent prompt <session> <text> [--wait] Send a prompt, optionally wait for it
|
|
4520
|
-
agent steer <session> <text> Redirect an
|
|
4521
|
-
agent stop <session> Stop an
|
|
5097
|
+
agent steer <session> <text> Redirect an active response (depth 0: any in scope)
|
|
5098
|
+
agent stop <session> Stop an active response (depth 0: any in scope)
|
|
4522
5099
|
agent spawn --prompt <text> [--agent n] [--model n] [--effort n]
|
|
4523
5100
|
Start a sibling with an explicit execution choice
|
|
4524
5101
|
agent spawn --prompt <text> --file <path> ...with context files it reads first
|
|
@@ -6904,6 +7481,293 @@ function decryptJsonWithCandidates(blob, keys) {
|
|
|
6904
7481
|
decryptCacheSet(blob, keys, null);
|
|
6905
7482
|
return null;
|
|
6906
7483
|
}
|
|
7484
|
+
function bounded(value2, maxLength) {
|
|
7485
|
+
if (typeof value2 !== "string" || value2.length === 0 || value2.length > maxLength) return void 0;
|
|
7486
|
+
return value2;
|
|
7487
|
+
}
|
|
7488
|
+
function boundedAllowEmpty(value2, maxLength) {
|
|
7489
|
+
if (typeof value2 !== "string" || value2.length > maxLength) return void 0;
|
|
7490
|
+
return value2;
|
|
7491
|
+
}
|
|
7492
|
+
function parsePasswordLoginResponse(raw) {
|
|
7493
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw) && raw.mfaRequired === true) {
|
|
7494
|
+
const value2 = raw;
|
|
7495
|
+
const loginToken = bounded(value2.loginToken, 512);
|
|
7496
|
+
if (loginToken === void 0) throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
|
|
7497
|
+
const methods = Array.isArray(value2.methods) ? value2.methods.filter((method) => typeof method === "string") : [];
|
|
7498
|
+
return {
|
|
7499
|
+
kind: "mfa-required",
|
|
7500
|
+
challenge: {
|
|
7501
|
+
loginToken,
|
|
7502
|
+
...typeof value2.expiresAt === "string" ? { expiresAt: value2.expiresAt } : {},
|
|
7503
|
+
methods,
|
|
7504
|
+
// 형이 어긋나면 보수적으로 "등록 필요" — 코드 입력을 헛되이 시키지 않는다.
|
|
7505
|
+
enrollmentRequired: value2.enrollmentRequired !== false
|
|
7506
|
+
}
|
|
7507
|
+
};
|
|
7508
|
+
}
|
|
7509
|
+
return { kind: "success", result: parseCloudLoginResult(raw) };
|
|
7510
|
+
}
|
|
7511
|
+
function parseKdfParams(value2) {
|
|
7512
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7513
|
+
const entries = Object.entries(value2);
|
|
7514
|
+
if (entries.length > 8) return void 0;
|
|
7515
|
+
const parsed = {};
|
|
7516
|
+
for (const [key, entry] of entries) {
|
|
7517
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(key)) return void 0;
|
|
7518
|
+
if (typeof entry === "string") {
|
|
7519
|
+
if (entry.length > 128) return void 0;
|
|
7520
|
+
} else if (typeof entry !== "number" && typeof entry !== "boolean") return void 0;
|
|
7521
|
+
parsed[key] = entry;
|
|
7522
|
+
}
|
|
7523
|
+
return parsed;
|
|
7524
|
+
}
|
|
7525
|
+
function parseUnlockMethod(value2) {
|
|
7526
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7527
|
+
const method = value2;
|
|
7528
|
+
const id = bounded(method.id, 128);
|
|
7529
|
+
const type = method.type;
|
|
7530
|
+
const kdfAlgorithm = bounded(method.kdfAlgorithm, 128);
|
|
7531
|
+
const kdfParams = parseKdfParams(method.kdfParams);
|
|
7532
|
+
const salt = boundedAllowEmpty(method.salt, 256);
|
|
7533
|
+
const wrappedMasterKey = bounded(method.wrappedMasterKey, 16384);
|
|
7534
|
+
if (!id || !/^[A-Za-z0-9_-]+$/.test(id) || type !== "password" && type !== "recovery" && type !== "device" || !kdfAlgorithm || !kdfParams || salt === void 0 || !wrappedMasterKey) return void 0;
|
|
7535
|
+
const revision = method.revision;
|
|
7536
|
+
if (revision !== void 0 && (!Number.isSafeInteger(revision) || revision < 1)) return void 0;
|
|
7537
|
+
return {
|
|
7538
|
+
id,
|
|
7539
|
+
type,
|
|
7540
|
+
kdfAlgorithm,
|
|
7541
|
+
kdfParams,
|
|
7542
|
+
salt,
|
|
7543
|
+
wrappedMasterKey,
|
|
7544
|
+
...revision === void 0 ? {} : { revision }
|
|
7545
|
+
};
|
|
7546
|
+
}
|
|
7547
|
+
function parseBundledAccountKey(value2) {
|
|
7548
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7549
|
+
const payload = value2;
|
|
7550
|
+
if (payload.mode !== "bundled" || !payload.material || typeof payload.material !== "object") return void 0;
|
|
7551
|
+
const material = payload.material;
|
|
7552
|
+
const keyVersion = material.keyVersion;
|
|
7553
|
+
const publicKey = bounded(material.publicKey, 128);
|
|
7554
|
+
const encryptedPrivateKey = bounded(material.encryptedPrivateKey, 16384);
|
|
7555
|
+
if (!Number.isSafeInteger(keyVersion) || keyVersion < 1 || !publicKey || !encryptedPrivateKey || !Array.isArray(material.unlockMethods) || material.unlockMethods.length > 16) return void 0;
|
|
7556
|
+
return {
|
|
7557
|
+
mode: "bundled",
|
|
7558
|
+
material: {
|
|
7559
|
+
keyVersion,
|
|
7560
|
+
publicKey,
|
|
7561
|
+
encryptedPrivateKey,
|
|
7562
|
+
unlockMethods: material.unlockMethods.flatMap((method) => {
|
|
7563
|
+
const parsed = parseUnlockMethod(method);
|
|
7564
|
+
return parsed ? [parsed] : [];
|
|
7565
|
+
})
|
|
7566
|
+
}
|
|
7567
|
+
};
|
|
7568
|
+
}
|
|
7569
|
+
function parseCloudLoginResult(raw) {
|
|
7570
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7571
|
+
throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
|
|
7572
|
+
}
|
|
7573
|
+
const value2 = raw;
|
|
7574
|
+
const required = (field, maxLength) => {
|
|
7575
|
+
const entry = bounded(value2[field], maxLength);
|
|
7576
|
+
if (entry === void 0) throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
|
|
7577
|
+
return entry;
|
|
7578
|
+
};
|
|
7579
|
+
const accountPublicKey = bounded(value2.accountPublicKey, 128);
|
|
7580
|
+
const accountKeySalt = bounded(value2.accountKeySalt, 128);
|
|
7581
|
+
const mfaSessionToken = bounded(value2.mfaSessionToken, 512);
|
|
7582
|
+
const accountKey = parseBundledAccountKey(value2.accountKey);
|
|
7583
|
+
return {
|
|
7584
|
+
userId: required("userId", 256),
|
|
7585
|
+
username: required("username", 256),
|
|
7586
|
+
token: required("token", 8192),
|
|
7587
|
+
secret: required("secret", 8192),
|
|
7588
|
+
...mfaSessionToken ? { mfaSessionToken } : {},
|
|
7589
|
+
...accountPublicKey ? { accountPublicKey } : {},
|
|
7590
|
+
...accountKeySalt ? { accountKeySalt } : {},
|
|
7591
|
+
...accountKey ? { accountKey } : {}
|
|
7592
|
+
};
|
|
7593
|
+
}
|
|
7594
|
+
const HANDOFF_ID_PATTERN = /^mph-[A-Za-z0-9_-]{22}$/;
|
|
7595
|
+
const CAPABILITY_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
7596
|
+
function record$5(value2) {
|
|
7597
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7598
|
+
}
|
|
7599
|
+
function validExpiry(value2) {
|
|
7600
|
+
return typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0;
|
|
7601
|
+
}
|
|
7602
|
+
function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
|
|
7603
|
+
const value2 = record$5(raw);
|
|
7604
|
+
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(value2.expiresAt)) {
|
|
7605
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7606
|
+
}
|
|
7607
|
+
let authorizationUrl;
|
|
7608
|
+
try {
|
|
7609
|
+
authorizationUrl = new URL(value2.authorizationUrl);
|
|
7610
|
+
const expectedOrigin = new URL(apiBaseUrl).origin;
|
|
7611
|
+
const fragmentParams = new URLSearchParams(authorizationUrl.hash.slice(1));
|
|
7612
|
+
const fragmentToken = fragmentParams.get("browserToken");
|
|
7613
|
+
if (authorizationUrl.origin !== expectedOrigin || authorizationUrl.username !== "" || authorizationUrl.password !== "" || authorizationUrl.pathname !== "/mfa/passkey-handoff" || [...authorizationUrl.searchParams].length !== 1 || authorizationUrl.searchParams.get("handoffId") !== value2.handoffId || [...fragmentParams].length !== 1 || !fragmentToken || !CAPABILITY_PATTERN.test(fragmentToken) || authorizationUrl.href.includes(value2.exchangeToken)) {
|
|
7614
|
+
throw new Error("invalid");
|
|
7615
|
+
}
|
|
7616
|
+
} catch {
|
|
7617
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7618
|
+
}
|
|
7619
|
+
return {
|
|
7620
|
+
handoffId: value2.handoffId,
|
|
7621
|
+
authorizationUrl: authorizationUrl.toString(),
|
|
7622
|
+
exchangeToken: value2.exchangeToken,
|
|
7623
|
+
expiresAt: value2.expiresAt
|
|
7624
|
+
};
|
|
7625
|
+
}
|
|
7626
|
+
function parsePendingMfaPasskeyExchange(raw) {
|
|
7627
|
+
const value2 = record$5(raw);
|
|
7628
|
+
if (!value2 || value2.status !== "pending" || !validExpiry(value2.expiresAt)) {
|
|
7629
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7630
|
+
}
|
|
7631
|
+
return { kind: "pending", expiresAt: value2.expiresAt };
|
|
7632
|
+
}
|
|
7633
|
+
const ENVELOPE_VERSION = 0;
|
|
7634
|
+
const EPHEMERAL_PUBLIC_KEY_LENGTH = 32;
|
|
7635
|
+
const ENVELOPE_NONCE_LENGTH = 24;
|
|
7636
|
+
const ENVELOPE_HEADER_LENGTH = 1 + EPHEMERAL_PUBLIC_KEY_LENGTH + ENVELOPE_NONCE_LENGTH;
|
|
7637
|
+
const GCM_VERSION = 0;
|
|
7638
|
+
const GCM_NONCE_LENGTH = 12;
|
|
7639
|
+
const GCM_TAG_LENGTH = 16;
|
|
7640
|
+
function unwrapDataEncryptionKey(envelopeBase64, recipientPrivateKey) {
|
|
7641
|
+
let envelope;
|
|
7642
|
+
try {
|
|
7643
|
+
envelope = decodeBase64(envelopeBase64);
|
|
7644
|
+
} catch {
|
|
7645
|
+
return null;
|
|
7646
|
+
}
|
|
7647
|
+
if (envelope.length <= ENVELOPE_HEADER_LENGTH) return null;
|
|
7648
|
+
if (envelope[0] !== ENVELOPE_VERSION) return null;
|
|
7649
|
+
const ephemeralPublicKey = envelope.subarray(1, 1 + EPHEMERAL_PUBLIC_KEY_LENGTH);
|
|
7650
|
+
const nonce = envelope.subarray(1 + EPHEMERAL_PUBLIC_KEY_LENGTH, ENVELOPE_HEADER_LENGTH);
|
|
7651
|
+
const ciphertext = envelope.subarray(ENVELOPE_HEADER_LENGTH);
|
|
7652
|
+
const opened = nacl.box.open(ciphertext, nonce, ephemeralPublicKey, recipientPrivateKey);
|
|
7653
|
+
return opened ? new Uint8Array(opened) : null;
|
|
7654
|
+
}
|
|
7655
|
+
async function importGcmKey(dataEncryptionKey) {
|
|
7656
|
+
return crypto.subtle.importKey("raw", new Uint8Array(dataEncryptionKey), "AES-GCM", false, ["encrypt", "decrypt"]);
|
|
7657
|
+
}
|
|
7658
|
+
async function decryptDataKeyToString(dataEncryptionKey, bundleBase64) {
|
|
7659
|
+
let bundle;
|
|
7660
|
+
try {
|
|
7661
|
+
bundle = decodeBase64(bundleBase64);
|
|
7662
|
+
} catch {
|
|
7663
|
+
return null;
|
|
7664
|
+
}
|
|
7665
|
+
if (bundle.length < 1 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) return null;
|
|
7666
|
+
if (bundle[0] !== GCM_VERSION) return null;
|
|
7667
|
+
const nonce = bundle.subarray(1, 1 + GCM_NONCE_LENGTH);
|
|
7668
|
+
const ciphertextWithTag = bundle.subarray(1 + GCM_NONCE_LENGTH);
|
|
7669
|
+
try {
|
|
7670
|
+
const key = await importGcmKey(dataEncryptionKey);
|
|
7671
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: new Uint8Array(nonce) }, key, new Uint8Array(ciphertextWithTag));
|
|
7672
|
+
return new TextDecoder().decode(plaintext);
|
|
7673
|
+
} catch {
|
|
7674
|
+
return null;
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
async function encryptDataKeyContent(dataEncryptionKey, value2) {
|
|
7678
|
+
const nonce = nacl.randomBytes(GCM_NONCE_LENGTH);
|
|
7679
|
+
const key = await importGcmKey(dataEncryptionKey);
|
|
7680
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(value2));
|
|
7681
|
+
const ciphertextWithTag = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: new Uint8Array(nonce) }, key, plaintext));
|
|
7682
|
+
const bundle = new Uint8Array(1 + GCM_NONCE_LENGTH + ciphertextWithTag.length);
|
|
7683
|
+
bundle[0] = GCM_VERSION;
|
|
7684
|
+
bundle.set(nonce, 1);
|
|
7685
|
+
bundle.set(ciphertextWithTag, 1 + GCM_NONCE_LENGTH);
|
|
7686
|
+
return encodeBase64(bundle);
|
|
7687
|
+
}
|
|
7688
|
+
let candidates = [];
|
|
7689
|
+
let version = 0;
|
|
7690
|
+
function getCompanyKeyCandidates() {
|
|
7691
|
+
return candidates.map((candidate) => candidate.privateKey);
|
|
7692
|
+
}
|
|
7693
|
+
function companyKeyChainVersion() {
|
|
7694
|
+
return version;
|
|
7695
|
+
}
|
|
7696
|
+
const dekBySessionId = /* @__PURE__ */ new Map();
|
|
7697
|
+
const lockedSessionIds = /* @__PURE__ */ new Set();
|
|
7698
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
7699
|
+
let boundaryGeneration = 0;
|
|
7700
|
+
function sessionDekRegistryGeneration() {
|
|
7701
|
+
return boundaryGeneration;
|
|
7702
|
+
}
|
|
7703
|
+
function registerSessionDek(session, accountPrivateKey) {
|
|
7704
|
+
const envelope = session.dataEncryptionKey ?? null;
|
|
7705
|
+
if (!envelope) {
|
|
7706
|
+
dekBySessionId.delete(session.id);
|
|
7707
|
+
lockedSessionIds.delete(session.id);
|
|
7708
|
+
fingerprints.delete(session.id);
|
|
7709
|
+
return;
|
|
7710
|
+
}
|
|
7711
|
+
const fingerprint = `${envelope}|${accountPrivateKey ? "1" : "0"}|${companyKeyChainVersion()}`;
|
|
7712
|
+
if (fingerprints.get(session.id) === fingerprint) return;
|
|
7713
|
+
if (!accountPrivateKey && dekBySessionId.has(session.id) && fingerprints.get(session.id)?.startsWith(`${envelope}|1|`)) return;
|
|
7714
|
+
const previousFingerprint = fingerprints.get(session.id);
|
|
7715
|
+
fingerprints.set(session.id, fingerprint);
|
|
7716
|
+
const candidates2 = accountPrivateKey ? [accountPrivateKey, ...getCompanyKeyCandidates()] : [];
|
|
7717
|
+
let dek = null;
|
|
7718
|
+
for (const candidate of candidates2) {
|
|
7719
|
+
dek = unwrapDataEncryptionKey(envelope, candidate);
|
|
7720
|
+
if (dek) break;
|
|
7721
|
+
}
|
|
7722
|
+
if (dek) {
|
|
7723
|
+
dekBySessionId.set(session.id, dek);
|
|
7724
|
+
lockedSessionIds.delete(session.id);
|
|
7725
|
+
return;
|
|
7726
|
+
}
|
|
7727
|
+
if (dekBySessionId.has(session.id) && previousFingerprint?.startsWith(`${envelope}|`)) return;
|
|
7728
|
+
dekBySessionId.delete(session.id);
|
|
7729
|
+
lockedSessionIds.add(session.id);
|
|
7730
|
+
}
|
|
7731
|
+
function getSessionDek(sessionId) {
|
|
7732
|
+
return dekBySessionId.get(sessionId) ?? null;
|
|
7733
|
+
}
|
|
7734
|
+
const MAX_ENTRIES = 3e3;
|
|
7735
|
+
const headerCache = /* @__PURE__ */ new Map();
|
|
7736
|
+
function cacheGet(store, sessionId, ciphertext) {
|
|
7737
|
+
return store.get(sessionId)?.get(ciphertext) ?? null;
|
|
7738
|
+
}
|
|
7739
|
+
function cacheSet(store, sessionId, ciphertext, plaintext) {
|
|
7740
|
+
let perSession = store.get(sessionId);
|
|
7741
|
+
if (!perSession) {
|
|
7742
|
+
perSession = /* @__PURE__ */ new Map();
|
|
7743
|
+
store.set(sessionId, perSession);
|
|
7744
|
+
}
|
|
7745
|
+
perSession.set(ciphertext, plaintext);
|
|
7746
|
+
if (perSession.size > MAX_ENTRIES) {
|
|
7747
|
+
const oldest = perSession.keys().next().value;
|
|
7748
|
+
if (oldest !== void 0) perSession.delete(oldest);
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
function getCachedDataKeyMetadataJson(sessionId, ciphertext) {
|
|
7752
|
+
return cacheGet(headerCache, sessionId, ciphertext);
|
|
7753
|
+
}
|
|
7754
|
+
async function preDecryptSessionHeaders(sessions, accountPrivateKey, sinceGeneration = sessionDekRegistryGeneration()) {
|
|
7755
|
+
const boundaryIntact = () => sessionDekRegistryGeneration() === sinceGeneration;
|
|
7756
|
+
await Promise.all(sessions.map(async (session) => {
|
|
7757
|
+
if (!boundaryIntact()) return;
|
|
7758
|
+
registerSessionDek(session, accountPrivateKey);
|
|
7759
|
+
const dek = getSessionDek(session.id);
|
|
7760
|
+
if (!dek) return;
|
|
7761
|
+
if (session.metadata && getCachedDataKeyMetadataJson(session.id, session.metadata) === null) {
|
|
7762
|
+
const json = await decryptDataKeyToString(dek, session.metadata);
|
|
7763
|
+
if (json !== null && boundaryIntact()) cacheSet(headerCache, session.id, session.metadata, json);
|
|
7764
|
+
}
|
|
7765
|
+
if (session.agentState && getCachedDataKeyMetadataJson(session.id, session.agentState) === null) {
|
|
7766
|
+
const json = await decryptDataKeyToString(dek, session.agentState);
|
|
7767
|
+
if (json !== null && boundaryIntact()) cacheSet(headerCache, session.id, session.agentState, json);
|
|
7768
|
+
}
|
|
7769
|
+
}));
|
|
7770
|
+
}
|
|
6907
7771
|
function buildUserTextMessageContent(input) {
|
|
6908
7772
|
return {
|
|
6909
7773
|
role: "user",
|
|
@@ -7022,69 +7886,69 @@ function personalMachinesPath(companyId) {
|
|
|
7022
7886
|
function myMachinesPath(companyId) {
|
|
7023
7887
|
return `/api/admin/companies/${encodeURIComponent(companyId)}/my-machines`;
|
|
7024
7888
|
}
|
|
7025
|
-
function record$
|
|
7889
|
+
function record$4(value2) {
|
|
7026
7890
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
7027
7891
|
}
|
|
7028
7892
|
function array(value2) {
|
|
7029
7893
|
return Array.isArray(value2) ? value2 : [];
|
|
7030
7894
|
}
|
|
7031
|
-
function str(value2, fallback = "") {
|
|
7895
|
+
function str$1(value2, fallback = "") {
|
|
7032
7896
|
return typeof value2 === "string" ? value2 : fallback;
|
|
7033
7897
|
}
|
|
7034
|
-
function num(value2, fallback = 0) {
|
|
7898
|
+
function num$1(value2, fallback = 0) {
|
|
7035
7899
|
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : fallback;
|
|
7036
7900
|
}
|
|
7037
7901
|
function nullableNum(value2) {
|
|
7038
7902
|
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : null;
|
|
7039
7903
|
}
|
|
7040
7904
|
function provider(value2) {
|
|
7041
|
-
return PROVIDERS.includes(str(value2)) ? str(value2) : null;
|
|
7905
|
+
return PROVIDERS.includes(str$1(value2)) ? str$1(value2) : null;
|
|
7042
7906
|
}
|
|
7043
7907
|
function parseBundle(value2) {
|
|
7044
|
-
const row2 = record$
|
|
7045
|
-
const id = str(row2.id);
|
|
7908
|
+
const row2 = record$4(value2);
|
|
7909
|
+
const id = str$1(row2.id);
|
|
7046
7910
|
const bundleProvider = provider(row2.provider);
|
|
7047
7911
|
if (!id || !bundleProvider) return null;
|
|
7048
7912
|
return {
|
|
7049
7913
|
id,
|
|
7050
|
-
companyId: str(row2.companyId),
|
|
7914
|
+
companyId: str$1(row2.companyId),
|
|
7051
7915
|
provider: bundleProvider,
|
|
7052
|
-
label: str(row2.label, id),
|
|
7053
|
-
formatVersion: num(row2.formatVersion, 1),
|
|
7054
|
-
accountCount: num(row2.accountCount),
|
|
7055
|
-
contentHash: str(row2.contentHash),
|
|
7056
|
-
version: num(row2.version, 1),
|
|
7057
|
-
createdById: str(row2.createdById),
|
|
7058
|
-
createdAt: num(row2.createdAt),
|
|
7059
|
-
updatedAt: num(row2.updatedAt)
|
|
7916
|
+
label: str$1(row2.label, id),
|
|
7917
|
+
formatVersion: num$1(row2.formatVersion, 1),
|
|
7918
|
+
accountCount: num$1(row2.accountCount),
|
|
7919
|
+
contentHash: str$1(row2.contentHash),
|
|
7920
|
+
version: num$1(row2.version, 1),
|
|
7921
|
+
createdById: str$1(row2.createdById),
|
|
7922
|
+
createdAt: num$1(row2.createdAt),
|
|
7923
|
+
updatedAt: num$1(row2.updatedAt)
|
|
7060
7924
|
};
|
|
7061
7925
|
}
|
|
7062
7926
|
function parseDeployment(value2) {
|
|
7063
|
-
const row2 = record$
|
|
7064
|
-
const machineId = str(row2.machineId);
|
|
7065
|
-
const bundleId = str(row2.bundleId);
|
|
7927
|
+
const row2 = record$4(value2);
|
|
7928
|
+
const machineId = str$1(row2.machineId);
|
|
7929
|
+
const bundleId = str$1(row2.bundleId);
|
|
7066
7930
|
if (!machineId || !bundleId) return null;
|
|
7067
|
-
const status = str(row2.status);
|
|
7931
|
+
const status = str$1(row2.status);
|
|
7068
7932
|
return {
|
|
7069
|
-
id: str(row2.id),
|
|
7070
|
-
companyId: str(row2.companyId),
|
|
7933
|
+
id: str$1(row2.id),
|
|
7934
|
+
companyId: str$1(row2.companyId),
|
|
7071
7935
|
bundleId,
|
|
7072
7936
|
machineId,
|
|
7073
7937
|
bundleVersion: nullableNum(row2.bundleVersion),
|
|
7074
|
-
attemptedBundleVersion: num(row2.attemptedBundleVersion, num(row2.bundleVersion)),
|
|
7938
|
+
attemptedBundleVersion: num$1(row2.attemptedBundleVersion, num$1(row2.bundleVersion)),
|
|
7075
7939
|
status: status === "deployed" || status === "failed" || status === "pending" ? status : "pending",
|
|
7076
7940
|
errorKind: typeof row2.errorKind === "string" ? row2.errorKind : null,
|
|
7077
7941
|
deployedAt: nullableNum(row2.deployedAt),
|
|
7078
|
-
createdAt: num(row2.createdAt),
|
|
7079
|
-
updatedAt: num(row2.updatedAt)
|
|
7942
|
+
createdAt: num$1(row2.createdAt),
|
|
7943
|
+
updatedAt: num$1(row2.updatedAt)
|
|
7080
7944
|
};
|
|
7081
7945
|
}
|
|
7082
7946
|
function parseStatus(value2) {
|
|
7083
|
-
const row2 = record$
|
|
7947
|
+
const row2 = record$4(value2);
|
|
7084
7948
|
const statusProvider = provider(row2.provider);
|
|
7085
7949
|
if (!statusProvider) return void 0;
|
|
7086
|
-
const rotationRow = record$
|
|
7087
|
-
const state = str(rotationRow.state);
|
|
7950
|
+
const rotationRow = record$4(row2.rotation);
|
|
7951
|
+
const state = str$1(rotationRow.state);
|
|
7088
7952
|
const rotation = ROTATION_STATES.includes(state) ? {
|
|
7089
7953
|
state,
|
|
7090
7954
|
lastErrorKind: typeof rotationRow.lastErrorKind === "string" ? rotationRow.lastErrorKind : null,
|
|
@@ -7104,11 +7968,11 @@ function parseStatus(value2) {
|
|
|
7104
7968
|
};
|
|
7105
7969
|
}
|
|
7106
7970
|
function parseMachineResult(value2) {
|
|
7107
|
-
const row2 = record$
|
|
7108
|
-
const machineId = str(row2.machineId);
|
|
7971
|
+
const row2 = record$4(value2);
|
|
7972
|
+
const machineId = str$1(row2.machineId);
|
|
7109
7973
|
if (!machineId) return null;
|
|
7110
7974
|
if (row2.ok !== true) {
|
|
7111
|
-
return { machineId, ok: false, errorKind: str(row2.errorKind, "UNKNOWN_ERROR") };
|
|
7975
|
+
return { machineId, ok: false, errorKind: str$1(row2.errorKind, "UNKNOWN_ERROR") };
|
|
7112
7976
|
}
|
|
7113
7977
|
const status = parseStatus(row2.status);
|
|
7114
7978
|
return {
|
|
@@ -7119,54 +7983,54 @@ function parseMachineResult(value2) {
|
|
|
7119
7983
|
};
|
|
7120
7984
|
}
|
|
7121
7985
|
function parseAiCredentialVaultSnapshot(value2) {
|
|
7122
|
-
const body = record$
|
|
7986
|
+
const body = record$4(value2);
|
|
7123
7987
|
return {
|
|
7124
7988
|
bundles: array(body.bundles).map(parseBundle).filter((bundle) => bundle !== null),
|
|
7125
7989
|
deployments: array(body.deployments).map(parseDeployment).filter((deployment) => deployment !== null)
|
|
7126
7990
|
};
|
|
7127
7991
|
}
|
|
7128
7992
|
function parseAiCredentialCaptureResponse(value2) {
|
|
7129
|
-
const body = record$
|
|
7993
|
+
const body = record$4(value2);
|
|
7130
7994
|
const bundle = parseBundle(body.bundle);
|
|
7131
7995
|
if (!bundle) throw new Error("AI credential capture response did not contain a bundle");
|
|
7132
7996
|
return { bundle, warnings: array(body.warnings).filter((warning) => typeof warning === "string") };
|
|
7133
7997
|
}
|
|
7134
7998
|
function parseAiCredentialBatchResult(value2) {
|
|
7135
|
-
const body = record$
|
|
7999
|
+
const body = record$4(value2);
|
|
7136
8000
|
return {
|
|
7137
8001
|
results: array(body.results).map(parseMachineResult).filter((result) => result !== null),
|
|
7138
8002
|
warnings: array(body.warnings).filter((warning) => typeof warning === "string")
|
|
7139
8003
|
};
|
|
7140
8004
|
}
|
|
7141
8005
|
function parseSelfServiceMachines(value2) {
|
|
7142
|
-
return array(record$
|
|
7143
|
-
const row2 = record$
|
|
8006
|
+
return array(record$4(value2).machines).map((entry) => {
|
|
8007
|
+
const row2 = record$4(entry);
|
|
7144
8008
|
return {
|
|
7145
|
-
machineId: str(row2.machineId),
|
|
8009
|
+
machineId: str$1(row2.machineId),
|
|
7146
8010
|
type: row2.type === "shared" ? "shared" : "personal",
|
|
7147
8011
|
displayName: typeof row2.displayName === "string" ? row2.displayName : null,
|
|
7148
8012
|
host: typeof row2.host === "string" ? row2.host : null,
|
|
7149
8013
|
workspaceRoot: typeof row2.workspaceRoot === "string" ? row2.workspaceRoot : null,
|
|
7150
|
-
registeredAt: num(row2.registeredAt),
|
|
8014
|
+
registeredAt: num$1(row2.registeredAt),
|
|
7151
8015
|
online: typeof row2.online === "boolean" ? row2.online : null
|
|
7152
8016
|
};
|
|
7153
8017
|
}).filter((machine) => machine.machineId !== "");
|
|
7154
8018
|
}
|
|
7155
8019
|
function parsePersonalMachines(value2) {
|
|
7156
|
-
return array(record$
|
|
7157
|
-
const row2 = record$
|
|
8020
|
+
return array(record$4(value2).machines).map((entry) => {
|
|
8021
|
+
const row2 = record$4(entry);
|
|
7158
8022
|
const type = row2.type === "shared" ? "shared" : "personal";
|
|
7159
8023
|
return {
|
|
7160
|
-
machineId: str(row2.machineId),
|
|
7161
|
-
ownerId: str(row2.ownerId),
|
|
8024
|
+
machineId: str$1(row2.machineId),
|
|
8025
|
+
ownerId: str$1(row2.ownerId),
|
|
7162
8026
|
ownerUsername: typeof row2.ownerUsername === "string" ? row2.ownerUsername : null,
|
|
7163
8027
|
ownerDisplayName: typeof row2.ownerDisplayName === "string" ? row2.ownerDisplayName : null,
|
|
7164
8028
|
type,
|
|
7165
8029
|
accessList: array(row2.accessList).filter((id) => typeof id === "string"),
|
|
7166
8030
|
displayName: typeof row2.displayName === "string" ? row2.displayName : null,
|
|
7167
8031
|
workspaceRoot: typeof row2.workspaceRoot === "string" ? row2.workspaceRoot : null,
|
|
7168
|
-
registeredAt: num(row2.registeredAt),
|
|
7169
|
-
updatedAt: num(row2.updatedAt),
|
|
8032
|
+
registeredAt: num$1(row2.registeredAt),
|
|
8033
|
+
updatedAt: num$1(row2.updatedAt),
|
|
7170
8034
|
online: typeof row2.online === "boolean" ? row2.online : null,
|
|
7171
8035
|
activeAt: nullableNum(row2.activeAt),
|
|
7172
8036
|
host: typeof row2.host === "string" ? row2.host : null,
|
|
@@ -7179,6 +8043,144 @@ function parsePersonalMachines(value2) {
|
|
|
7179
8043
|
};
|
|
7180
8044
|
}).filter((machine) => machine.machineId !== "");
|
|
7181
8045
|
}
|
|
8046
|
+
const orgSearchPaths = {
|
|
8047
|
+
/** Fetched by main only (secrets) — kept here so the path has one origin. */
|
|
8048
|
+
config: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-config`,
|
|
8049
|
+
reissue: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-config/reissue`,
|
|
8050
|
+
integration: (companyId) => `/api/admin/companies/${encodeURIComponent(companyId)}/search-integration`,
|
|
8051
|
+
acl: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-acl`,
|
|
8052
|
+
leases: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/leases`,
|
|
8053
|
+
cursors: (companyId, projectId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/cursors${projectId === void 0 ? "" : `?projectId=${encodeURIComponent(projectId)}`}`,
|
|
8054
|
+
progress: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/progress`
|
|
8055
|
+
};
|
|
8056
|
+
function record$3(value2) {
|
|
8057
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
8058
|
+
}
|
|
8059
|
+
function str(value2, fallback = "") {
|
|
8060
|
+
return typeof value2 === "string" ? value2 : fallback;
|
|
8061
|
+
}
|
|
8062
|
+
function num(value2, fallback = 0) {
|
|
8063
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : fallback;
|
|
8064
|
+
}
|
|
8065
|
+
const PHASES = ["idle", "waiting", "indexing", "done"];
|
|
8066
|
+
function parseOrgSearchCursors(raw) {
|
|
8067
|
+
const list = Array.isArray(record$3(raw).cursors) ? record$3(raw).cursors : [];
|
|
8068
|
+
return list.flatMap((item) => {
|
|
8069
|
+
const c = record$3(item);
|
|
8070
|
+
if (typeof c.sessionId !== "string" || typeof c.lastSeq !== "number") return [];
|
|
8071
|
+
return [{
|
|
8072
|
+
sessionId: c.sessionId,
|
|
8073
|
+
lastSeq: c.lastSeq,
|
|
8074
|
+
complete: c.complete === true,
|
|
8075
|
+
failureKind: typeof c.failureKind === "string" ? c.failureKind : null,
|
|
8076
|
+
updatedAt: num(c.updatedAt)
|
|
8077
|
+
}];
|
|
8078
|
+
});
|
|
8079
|
+
}
|
|
8080
|
+
function parseProjectProgress(raw) {
|
|
8081
|
+
const p = record$3(raw);
|
|
8082
|
+
return {
|
|
8083
|
+
projectId: str(p.projectId),
|
|
8084
|
+
sessionsTotal: num(p.sessionsTotal),
|
|
8085
|
+
sessionsIndexed: num(p.sessionsIndexed),
|
|
8086
|
+
sessionsFailed: num(p.sessionsFailed),
|
|
8087
|
+
messagesIndexed: num(p.messagesIndexed),
|
|
8088
|
+
phase: PHASES.includes(p.phase) ? p.phase : "idle",
|
|
8089
|
+
leaseHolderMasked: typeof p.leaseHolderMasked === "string" ? p.leaseHolderMasked : null,
|
|
8090
|
+
updatedAt: typeof p.updatedAt === "number" ? p.updatedAt : null
|
|
8091
|
+
};
|
|
8092
|
+
}
|
|
8093
|
+
function parseOrgSearchProgress(raw) {
|
|
8094
|
+
const r = record$3(raw);
|
|
8095
|
+
return {
|
|
8096
|
+
phase: PHASES.includes(r.phase) ? r.phase : "idle",
|
|
8097
|
+
sessionsTotal: num(r.sessionsTotal),
|
|
8098
|
+
sessionsIndexed: num(r.sessionsIndexed),
|
|
8099
|
+
sessionsFailed: num(r.sessionsFailed),
|
|
8100
|
+
messagesIndexed: num(r.messagesIndexed),
|
|
8101
|
+
activeLeases: num(r.activeLeases),
|
|
8102
|
+
updatedAt: typeof r.updatedAt === "number" ? r.updatedAt : null,
|
|
8103
|
+
projects: (Array.isArray(r.projects) ? r.projects : []).map(parseProjectProgress)
|
|
8104
|
+
};
|
|
8105
|
+
}
|
|
8106
|
+
function isApiError(error) {
|
|
8107
|
+
return error instanceof Error && error.name === "ApiError" && typeof error.status === "number" && typeof error.body === "string";
|
|
8108
|
+
}
|
|
8109
|
+
function errorCode(error) {
|
|
8110
|
+
try {
|
|
8111
|
+
const parsed = JSON.parse(error.body);
|
|
8112
|
+
return typeof parsed.error === "string" ? parsed.error : null;
|
|
8113
|
+
} catch {
|
|
8114
|
+
return null;
|
|
8115
|
+
}
|
|
8116
|
+
}
|
|
8117
|
+
function classify404(error) {
|
|
8118
|
+
const code = errorCode(error);
|
|
8119
|
+
if (code === "search-integration-not-configured") return { kind: "not-configured" };
|
|
8120
|
+
if (code === "project-not-found") return { kind: "not-found" };
|
|
8121
|
+
return { kind: "unsupported" };
|
|
8122
|
+
}
|
|
8123
|
+
async function attempt(run, onError) {
|
|
8124
|
+
try {
|
|
8125
|
+
return await run();
|
|
8126
|
+
} catch (error) {
|
|
8127
|
+
if (isApiError(error)) {
|
|
8128
|
+
const mapped = onError(error);
|
|
8129
|
+
if (mapped !== null) return mapped;
|
|
8130
|
+
}
|
|
8131
|
+
throw error;
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
function createOrgSearchApi(transport) {
|
|
8135
|
+
const json = (body, method) => ({ method, body: JSON.stringify(body) });
|
|
8136
|
+
return {
|
|
8137
|
+
/** Member whose key returned 401 asks the reconciler for a new one (state → reissue). */
|
|
8138
|
+
requestKeyReissue: (companyId) => attempt(
|
|
8139
|
+
async () => {
|
|
8140
|
+
await transport.request(orgSearchPaths.reissue(companyId), { method: "POST" });
|
|
8141
|
+
return { kind: "ok" };
|
|
8142
|
+
},
|
|
8143
|
+
(error) => error.status === 404 ? errorCode(error) === "member-key-not-found" ? { kind: "not-found" } : { kind: "unsupported" } : null
|
|
8144
|
+
),
|
|
8145
|
+
getIntegration: (companyId) => attempt(
|
|
8146
|
+
async () => ({ kind: "ok", integration: await transport.request(orgSearchPaths.integration(companyId)) }),
|
|
8147
|
+
(error) => error.status === 404 ? classify404(error) : null
|
|
8148
|
+
),
|
|
8149
|
+
deleteIntegration: async (companyId) => {
|
|
8150
|
+
await transport.request(orgSearchPaths.integration(companyId), { method: "DELETE" });
|
|
8151
|
+
},
|
|
8152
|
+
getAcl: (companyId) => transport.request(orgSearchPaths.acl(companyId)),
|
|
8153
|
+
putAcl: (companyId, input) => attempt(
|
|
8154
|
+
async () => {
|
|
8155
|
+
await transport.request(orgSearchPaths.acl(companyId), json(input, "PUT"));
|
|
8156
|
+
return { kind: "ok" };
|
|
8157
|
+
},
|
|
8158
|
+
(error) => error.status === 409 ? { kind: "lease-not-held" } : error.status === 404 ? { kind: "unsupported" } : null
|
|
8159
|
+
),
|
|
8160
|
+
lease: (companyId, projectId, action) => attempt(
|
|
8161
|
+
async () => {
|
|
8162
|
+
const lease = await transport.request(orgSearchPaths.leases(companyId), json({ projectId, action }, "POST"));
|
|
8163
|
+
return action === "release" ? { kind: "released" } : { kind: "ok", lease };
|
|
8164
|
+
},
|
|
8165
|
+
(error) => error.status === 404 ? classify404(error) : error.status === 403 ? { kind: "forbidden" } : null
|
|
8166
|
+
),
|
|
8167
|
+
getCursors: (companyId, projectId) => attempt(
|
|
8168
|
+
async () => ({ kind: "ok", cursors: parseOrgSearchCursors(await transport.request(orgSearchPaths.cursors(companyId, projectId))) }),
|
|
8169
|
+
(error) => error.status === 404 ? classify404(error) : null
|
|
8170
|
+
),
|
|
8171
|
+
putCursors: (companyId, projectId, cursors) => attempt(
|
|
8172
|
+
async () => {
|
|
8173
|
+
await transport.request(orgSearchPaths.cursors(companyId), json({ projectId, cursors }, "PUT"));
|
|
8174
|
+
return { kind: "ok" };
|
|
8175
|
+
},
|
|
8176
|
+
(error) => error.status === 409 ? { kind: "lease-not-held" } : error.status === 404 ? classify404(error) : null
|
|
8177
|
+
),
|
|
8178
|
+
getProgress: (companyId) => attempt(
|
|
8179
|
+
async () => ({ kind: "ok", progress: parseOrgSearchProgress(await transport.request(orgSearchPaths.progress(companyId))) }),
|
|
8180
|
+
(error) => error.status === 404 ? { kind: "unsupported" } : null
|
|
8181
|
+
)
|
|
8182
|
+
};
|
|
8183
|
+
}
|
|
7182
8184
|
const SERVER_MACHINE_CONCURRENCY = 4;
|
|
7183
8185
|
const MAX_FANOUT_TIMEOUT_MS = 9e5;
|
|
7184
8186
|
function machineFanoutTimeoutMs(perMachineMs, machineCount) {
|
|
@@ -7524,6 +8526,123 @@ function parseProjectListV2Page(body) {
|
|
|
7524
8526
|
total: page.total
|
|
7525
8527
|
};
|
|
7526
8528
|
}
|
|
8529
|
+
const PUBLIC_ID_PATTERN = /^[A-Za-z0-9_-]{32}$/;
|
|
8530
|
+
const CHECKSUM_PATTERN = /^[a-f\d]{64}$/i;
|
|
8531
|
+
const MAX_ID_LENGTH = 256;
|
|
8532
|
+
const MAX_TITLE_LENGTH = 255;
|
|
8533
|
+
const MAX_WARNINGS = 32;
|
|
8534
|
+
const MAX_WARNING_LENGTH = 500;
|
|
8535
|
+
const PUBLICATION_KEYS = /* @__PURE__ */ new Set([
|
|
8536
|
+
"id",
|
|
8537
|
+
"publicId",
|
|
8538
|
+
"sourceType",
|
|
8539
|
+
"sourceIdentity",
|
|
8540
|
+
"sourceChecksumSha256",
|
|
8541
|
+
"title",
|
|
8542
|
+
"contentType",
|
|
8543
|
+
"sizeBytes",
|
|
8544
|
+
"checksumSha256",
|
|
8545
|
+
"status",
|
|
8546
|
+
"createdAt",
|
|
8547
|
+
"revokedAt",
|
|
8548
|
+
"publicUrl"
|
|
8549
|
+
]);
|
|
8550
|
+
function fail(detail) {
|
|
8551
|
+
throw new Error(`artifact publication response is invalid: ${detail}`);
|
|
8552
|
+
}
|
|
8553
|
+
function record$2(value2, label) {
|
|
8554
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) fail(`${label} must be an object`);
|
|
8555
|
+
return value2;
|
|
8556
|
+
}
|
|
8557
|
+
function exactKeys(value2, keys) {
|
|
8558
|
+
const expected = new Set(keys);
|
|
8559
|
+
if (Object.keys(value2).some((key) => !expected.has(key)) || keys.some((key) => !(key in value2))) fail("response fields do not match the contract");
|
|
8560
|
+
}
|
|
8561
|
+
function nonEmptyString$1(value2, label, maxLength = MAX_ID_LENGTH) {
|
|
8562
|
+
if (typeof value2 !== "string" || !value2 || value2.trim() !== value2 || value2.length > maxLength) fail(`${label} is invalid`);
|
|
8563
|
+
return value2;
|
|
8564
|
+
}
|
|
8565
|
+
function checksum(value2, label) {
|
|
8566
|
+
if (typeof value2 !== "string" || !CHECKSUM_PATTERN.test(value2)) fail(`${label} is invalid`);
|
|
8567
|
+
}
|
|
8568
|
+
function safeInteger(value2, label) {
|
|
8569
|
+
if (typeof value2 !== "number" || !Number.isSafeInteger(value2) || value2 < 0) fail(`${label} is invalid`);
|
|
8570
|
+
}
|
|
8571
|
+
function warnings(value2) {
|
|
8572
|
+
if (!Array.isArray(value2) || value2.length > MAX_WARNINGS || value2.some((warning) => typeof warning !== "string" || warning.length > MAX_WARNING_LENGTH)) fail("warnings are invalid");
|
|
8573
|
+
return [...value2];
|
|
8574
|
+
}
|
|
8575
|
+
function isLoopbackHostname(hostname) {
|
|
8576
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
8577
|
+
}
|
|
8578
|
+
function parseTrustedPublicUrl(value2, apiBaseUrl, publicId) {
|
|
8579
|
+
if (typeof value2 !== "string") fail("publicUrl is invalid");
|
|
8580
|
+
let publicUrl;
|
|
8581
|
+
let apiUrl;
|
|
8582
|
+
try {
|
|
8583
|
+
publicUrl = new URL(value2);
|
|
8584
|
+
apiUrl = new URL(apiBaseUrl);
|
|
8585
|
+
} catch {
|
|
8586
|
+
fail("publicUrl is invalid");
|
|
8587
|
+
}
|
|
8588
|
+
if (publicUrl.protocol !== "https:" && !(publicUrl.protocol === "http:" && isLoopbackHostname(publicUrl.hostname)) || publicUrl.origin !== apiUrl.origin || publicUrl.protocol !== apiUrl.protocol || publicUrl.username || publicUrl.password || publicUrl.search || publicUrl.hash || publicUrl.pathname !== `/artifacts/${publicId}`) fail("publicUrl is not trusted");
|
|
8589
|
+
return publicUrl.toString();
|
|
8590
|
+
}
|
|
8591
|
+
function sourceIdentity(source) {
|
|
8592
|
+
return source.type === "project-file" ? JSON.stringify([
|
|
8593
|
+
source.projectId,
|
|
8594
|
+
...source.worktreeId ? [source.worktreeId] : [],
|
|
8595
|
+
source.path
|
|
8596
|
+
]) : JSON.stringify([source.chatId, source.path]);
|
|
8597
|
+
}
|
|
8598
|
+
function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
|
|
8599
|
+
const publication = record$2(value2, "publication");
|
|
8600
|
+
if (Object.keys(publication).some((key) => !PUBLICATION_KEYS.has(key))) {
|
|
8601
|
+
fail("publication contains an unexpected field");
|
|
8602
|
+
}
|
|
8603
|
+
if ([...PUBLICATION_KEYS].some((key) => !(key in publication))) {
|
|
8604
|
+
fail("publication is missing a field");
|
|
8605
|
+
}
|
|
8606
|
+
const publicationId = nonEmptyString$1(publication.id, "publication id");
|
|
8607
|
+
const publicId = nonEmptyString$1(publication.publicId, "public id");
|
|
8608
|
+
if (!PUBLIC_ID_PATTERN.test(publicId)) fail("public id is invalid");
|
|
8609
|
+
if (!["project-file", "personal-chat-file", "personal-chat-artifact"].includes(String(publication.sourceType))) {
|
|
8610
|
+
fail("source type is invalid");
|
|
8611
|
+
}
|
|
8612
|
+
const parsedSourceIdentity = nonEmptyString$1(publication.sourceIdentity, "source identity", 4096);
|
|
8613
|
+
if (expectedSource && (publication.sourceType !== expectedSource.type || parsedSourceIdentity !== sourceIdentity(expectedSource))) fail("source does not match the request");
|
|
8614
|
+
checksum(publication.sourceChecksumSha256, "source checksum");
|
|
8615
|
+
const title = nonEmptyString$1(publication.title, "title", MAX_TITLE_LENGTH);
|
|
8616
|
+
nonEmptyString$1(publication.contentType, "content type");
|
|
8617
|
+
safeInteger(publication.sizeBytes, "size");
|
|
8618
|
+
checksum(publication.checksumSha256, "checksum");
|
|
8619
|
+
safeInteger(publication.createdAt, "created time");
|
|
8620
|
+
const expectedStatus = operation === "publish" ? "active" : "revoked";
|
|
8621
|
+
if (publication.status !== expectedStatus) fail("status does not match the operation");
|
|
8622
|
+
if (publication.revokedAt !== null && typeof publication.revokedAt !== "number" || typeof publication.revokedAt === "number" && (!Number.isSafeInteger(publication.revokedAt) || publication.revokedAt < 0) || expectedStatus === "active" && publication.revokedAt !== null || expectedStatus === "revoked" && publication.revokedAt === null) fail("revoked time is invalid");
|
|
8623
|
+
return {
|
|
8624
|
+
publicationId,
|
|
8625
|
+
publicId,
|
|
8626
|
+
publicUrl: parseTrustedPublicUrl(publication.publicUrl, apiBaseUrl, publicId),
|
|
8627
|
+
title,
|
|
8628
|
+
status: expectedStatus
|
|
8629
|
+
};
|
|
8630
|
+
}
|
|
8631
|
+
function parseStaticArtifactPublicationResult(value2, options) {
|
|
8632
|
+
const response = record$2(value2, "response");
|
|
8633
|
+
if (options.operation === "publish") {
|
|
8634
|
+
exactKeys(response, ["publication", "publicUrl", "reused"]);
|
|
8635
|
+
if (typeof response.reused !== "boolean") fail("reused is invalid");
|
|
8636
|
+
const publication2 = validatePublication(response.publication, options.apiBaseUrl, options.operation, options.source);
|
|
8637
|
+
const publicUrl = parseTrustedPublicUrl(response.publicUrl, options.apiBaseUrl, publication2.publicId);
|
|
8638
|
+
if (publication2.publicUrl !== publicUrl) fail("public URLs do not match");
|
|
8639
|
+
return { version: 1, ...publication2, reused: response.reused, warnings: [] };
|
|
8640
|
+
}
|
|
8641
|
+
exactKeys(response, ["publication", "warnings"]);
|
|
8642
|
+
const parsedWarnings = warnings(response.warnings);
|
|
8643
|
+
const publication = validatePublication(response.publication, options.apiBaseUrl, options.operation);
|
|
8644
|
+
return { version: 1, ...publication, reused: false, warnings: parsedWarnings };
|
|
8645
|
+
}
|
|
7527
8646
|
function asRecord(value2) {
|
|
7528
8647
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7529
8648
|
}
|
|
@@ -11067,14 +12186,14 @@ function requireReceiver() {
|
|
|
11067
12186
|
* @return {(Error|RangeError)} The error
|
|
11068
12187
|
* @private
|
|
11069
12188
|
*/
|
|
11070
|
-
createError(ErrorCtor, message, prefix, statusCode,
|
|
12189
|
+
createError(ErrorCtor, message, prefix, statusCode, errorCode2) {
|
|
11071
12190
|
this._loop = false;
|
|
11072
12191
|
this._errored = true;
|
|
11073
12192
|
const err = new ErrorCtor(
|
|
11074
12193
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
|
11075
12194
|
);
|
|
11076
12195
|
Error.captureStackTrace(err, this.createError);
|
|
11077
|
-
err.code =
|
|
12196
|
+
err.code = errorCode2;
|
|
11078
12197
|
err[kStatusCode] = statusCode;
|
|
11079
12198
|
return err;
|
|
11080
12199
|
}
|
|
@@ -15845,10 +16964,10 @@ class Manager extends Emitter {
|
|
|
15845
16964
|
* @private
|
|
15846
16965
|
*/
|
|
15847
16966
|
onreconnect() {
|
|
15848
|
-
const
|
|
16967
|
+
const attempt2 = this.backoff.attempts;
|
|
15849
16968
|
this._reconnecting = false;
|
|
15850
16969
|
this.backoff.reset();
|
|
15851
|
-
this.emitReserved("reconnect",
|
|
16970
|
+
this.emitReserved("reconnect", attempt2);
|
|
15852
16971
|
}
|
|
15853
16972
|
}
|
|
15854
16973
|
const debug = debugModule("socket.io-client");
|
|
@@ -16132,6 +17251,18 @@ function coerceCreatedBy(value2) {
|
|
|
16132
17251
|
const displayName = nonEmptyString(value2.displayName);
|
|
16133
17252
|
return displayName ? { accountId, displayName } : { accountId };
|
|
16134
17253
|
}
|
|
17254
|
+
function decryptSessionJsonSync(sessionId, blob, candidateKeys) {
|
|
17255
|
+
if (!blob) return null;
|
|
17256
|
+
const cached = getCachedDataKeyMetadataJson(sessionId, blob);
|
|
17257
|
+
if (cached !== null) {
|
|
17258
|
+
try {
|
|
17259
|
+
return JSON.parse(cached);
|
|
17260
|
+
} catch {
|
|
17261
|
+
return null;
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
17264
|
+
return decryptJsonWithCandidates(blob, candidateKeys);
|
|
17265
|
+
}
|
|
16135
17266
|
function apiSessionToSession(api, candidateKeys, existing) {
|
|
16136
17267
|
const updatedAt = mergeUpdatedAt(api.updatedAt);
|
|
16137
17268
|
const summaryText = decryptSummaryText(api.metadata, candidateKeys) ?? existing?.summaryText;
|
|
@@ -16141,8 +17272,8 @@ function apiSessionToSession(api, candidateKeys, existing) {
|
|
|
16141
17272
|
const pendingExitPlanMode = apiSessionAgentStatePendingExitPlanMode(api.agentState, candidateKeys);
|
|
16142
17273
|
const pendingPermissionRequests = apiSessionAgentStatePendingPermissions(api.agentState, candidateKeys);
|
|
16143
17274
|
const agentControlledByUser = apiSessionAgentStateControlledByUser(api.agentState, candidateKeys);
|
|
16144
|
-
const agentState = api.agentState ?
|
|
16145
|
-
const metadata =
|
|
17275
|
+
const agentState = api.agentState ? decryptSessionJsonSync(api.id, api.agentState, candidateKeys) : null;
|
|
17276
|
+
const metadata = decryptSessionJsonSync(api.id, api.metadata, candidateKeys);
|
|
16146
17277
|
const agent = agentFromFlavor(typeof metadata?.flavor === "string" ? metadata.flavor : void 0) ?? existing?.agent;
|
|
16147
17278
|
const models = coerceSessionModels(metadata?.models) ?? existing?.models;
|
|
16148
17279
|
const worktree = coerceSessionWorktree(metadata?.worktree) ?? coerceSessionWorktree(metadata) ?? existing?.worktree;
|
|
@@ -16235,6 +17366,16 @@ function buildWorktreeMutationBody(input, options = {}) {
|
|
|
16235
17366
|
Object.entries(input).filter(([key, value2]) => (key !== "machinePlatform" || options.includeMachinePlatform) && (typeof value2 === "string" && (value2.length > 0 || CLEARABLE_STRING_KEYS.has(key)) || value2 === null && CLEARABLE_STRING_KEYS.has(key)))
|
|
16236
17367
|
));
|
|
16237
17368
|
}
|
|
17369
|
+
function readDaemonRunning(decoded) {
|
|
17370
|
+
if (!decoded || typeof decoded !== "object") return null;
|
|
17371
|
+
const { status, state } = decoded;
|
|
17372
|
+
return knownDaemonState(status) ?? knownDaemonState(state);
|
|
17373
|
+
}
|
|
17374
|
+
function knownDaemonState(value2) {
|
|
17375
|
+
if (value2 === "running") return true;
|
|
17376
|
+
if (value2 === "stopped" || value2 === "shutting-down") return false;
|
|
17377
|
+
return null;
|
|
17378
|
+
}
|
|
16238
17379
|
const STANDALONE_USER_ID = "standalone-local";
|
|
16239
17380
|
function isStandaloneAuthSession(session) {
|
|
16240
17381
|
return session?.userId === STANDALONE_USER_ID;
|
|
@@ -16318,19 +17459,19 @@ function row(value2) {
|
|
|
16318
17459
|
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("automation-response-invalid");
|
|
16319
17460
|
return value2;
|
|
16320
17461
|
}
|
|
16321
|
-
function stringValue(value2) {
|
|
17462
|
+
function stringValue$1(value2) {
|
|
16322
17463
|
if (typeof value2 !== "string" || !value2) throw new Error("automation-response-invalid");
|
|
16323
17464
|
return value2;
|
|
16324
17465
|
}
|
|
16325
17466
|
function nullableString(value2) {
|
|
16326
|
-
return value2 === null ? null : stringValue(value2);
|
|
17467
|
+
return value2 === null ? null : stringValue$1(value2);
|
|
16327
17468
|
}
|
|
16328
|
-
function integer(value2, min = 0) {
|
|
17469
|
+
function integer$1(value2, min = 0) {
|
|
16329
17470
|
if (!Number.isSafeInteger(value2) || value2 < min) throw new Error("automation-response-invalid");
|
|
16330
17471
|
return value2;
|
|
16331
17472
|
}
|
|
16332
17473
|
function nullableInteger(value2) {
|
|
16333
|
-
return value2 === null ? null : integer(value2);
|
|
17474
|
+
return value2 === null ? null : integer$1(value2);
|
|
16334
17475
|
}
|
|
16335
17476
|
function optionalNullableInteger(value2) {
|
|
16336
17477
|
return value2 === void 0 ? null : nullableInteger(value2);
|
|
@@ -16338,40 +17479,40 @@ function optionalNullableInteger(value2) {
|
|
|
16338
17479
|
function parseRuntimeState(value2) {
|
|
16339
17480
|
const item = row(value2);
|
|
16340
17481
|
return {
|
|
16341
|
-
automationId: stringValue(item.automationId),
|
|
16342
|
-
generation: integer(item.generation, 1),
|
|
16343
|
-
stateVersion: integer(item.stateVersion, 1),
|
|
17482
|
+
automationId: stringValue$1(item.automationId),
|
|
17483
|
+
generation: integer$1(item.generation, 1),
|
|
17484
|
+
stateVersion: integer$1(item.stateVersion, 1),
|
|
16344
17485
|
stateCiphertext: versionedAutomationBase64(item.stateCiphertext, 96 * 1024),
|
|
16345
|
-
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
17486
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion, 1),
|
|
16346
17487
|
viewerKeyEnvelope: versionedAutomationBase64(item.viewerKeyEnvelope, 105, 105),
|
|
16347
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
16348
|
-
updatedAt: integer(item.updatedAt)
|
|
17488
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
17489
|
+
updatedAt: integer$1(item.updatedAt)
|
|
16349
17490
|
};
|
|
16350
17491
|
}
|
|
16351
17492
|
function parseServerAutomationPublic(value2) {
|
|
16352
17493
|
const item = row(value2);
|
|
16353
17494
|
if (item.payloadVersion !== 1 && item.payloadVersion !== 2 || typeof item.paused !== "boolean") throw new Error("automation-response-invalid");
|
|
16354
17495
|
const parsed = {
|
|
16355
|
-
id: stringValue(item.id),
|
|
16356
|
-
projectId: stringValue(item.projectId),
|
|
16357
|
-
ownerAccountId: stringValue(item.ownerAccountId),
|
|
17496
|
+
id: stringValue$1(item.id),
|
|
17497
|
+
projectId: stringValue$1(item.projectId),
|
|
17498
|
+
ownerAccountId: stringValue$1(item.ownerAccountId),
|
|
16358
17499
|
machineAccountId: nullableString(item.machineAccountId),
|
|
16359
17500
|
machineId: nullableString(item.machineId),
|
|
16360
|
-
revision: integer(item.revision, 1),
|
|
16361
|
-
generation: integer(item.generation, 1),
|
|
17501
|
+
revision: integer$1(item.revision, 1),
|
|
17502
|
+
generation: integer$1(item.generation, 1),
|
|
16362
17503
|
payloadVersion: item.payloadVersion,
|
|
16363
|
-
payloadCiphertext: stringValue(item.payloadCiphertext),
|
|
16364
|
-
viewerKeyId: stringValue(item.viewerKeyId),
|
|
16365
|
-
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
16366
|
-
viewerKeyEnvelope: stringValue(item.viewerKeyEnvelope),
|
|
16367
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17504
|
+
payloadCiphertext: stringValue$1(item.payloadCiphertext),
|
|
17505
|
+
viewerKeyId: stringValue$1(item.viewerKeyId),
|
|
17506
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion, 1),
|
|
17507
|
+
viewerKeyEnvelope: stringValue$1(item.viewerKeyEnvelope),
|
|
17508
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
16368
17509
|
paused: item.paused,
|
|
16369
|
-
enabledAt: integer(item.enabledAt),
|
|
17510
|
+
enabledAt: integer$1(item.enabledAt),
|
|
16370
17511
|
runRequestedAt: optionalNullableInteger(item.runRequestedAt),
|
|
16371
|
-
appliedRevision: integer(item.appliedRevision),
|
|
17512
|
+
appliedRevision: integer$1(item.appliedRevision),
|
|
16372
17513
|
appliedAt: nullableInteger(item.appliedAt),
|
|
16373
|
-
createdAt: integer(item.createdAt),
|
|
16374
|
-
updatedAt: integer(item.updatedAt),
|
|
17514
|
+
createdAt: integer$1(item.createdAt),
|
|
17515
|
+
updatedAt: integer$1(item.updatedAt),
|
|
16375
17516
|
runtimeState: item.runtimeState === void 0 || item.runtimeState === null ? null : parseRuntimeState(item.runtimeState)
|
|
16376
17517
|
};
|
|
16377
17518
|
if (parsed.payloadVersion === 2 !== (parsed.runtimeState !== null) || parsed.runtimeState && (parsed.runtimeState.automationId !== parsed.id || parsed.runtimeState.generation !== parsed.generation || parsed.runtimeState.viewerKeyVersion !== parsed.viewerKeyVersion || parsed.runtimeState.machineKeyVersion !== parsed.machineKeyVersion)) {
|
|
@@ -16393,13 +17534,14 @@ function parseServerAutomationAdoption(value2) {
|
|
|
16393
17534
|
function parseServerAutomationTarget(value2) {
|
|
16394
17535
|
const item = row(value2);
|
|
16395
17536
|
return {
|
|
16396
|
-
machineAccountId: stringValue(item.machineAccountId),
|
|
16397
|
-
machineId: stringValue(item.machineId),
|
|
16398
|
-
machinePublicKey: stringValue(item.machinePublicKey),
|
|
16399
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17537
|
+
machineAccountId: stringValue$1(item.machineAccountId),
|
|
17538
|
+
machineId: stringValue$1(item.machineId),
|
|
17539
|
+
machinePublicKey: stringValue$1(item.machinePublicKey),
|
|
17540
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
16400
17541
|
viewerPublicKey: nullableString(item.viewerPublicKey),
|
|
16401
|
-
viewerKeyVersion: integer(item.viewerKeyVersion),
|
|
16402
|
-
automationProtocolVersion: item.automationProtocolVersion === void 0 ? 1 : integer(item.automationProtocolVersion, 1)
|
|
17542
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion),
|
|
17543
|
+
automationProtocolVersion: item.automationProtocolVersion === void 0 ? 1 : integer$1(item.automationProtocolVersion, 1),
|
|
17544
|
+
sessionFollowupSupported: item.sessionFollowupSupported === true
|
|
16403
17545
|
};
|
|
16404
17546
|
}
|
|
16405
17547
|
const RUN_STATUSES = /* @__PURE__ */ new Set(["CLAIMED", "RUNNING", "COMPLETED", "FAILED", "CANCELLED", "EXPIRED", "ABANDONED"]);
|
|
@@ -16408,16 +17550,16 @@ function parseServerAutomationRun(value2) {
|
|
|
16408
17550
|
const item = row(value2);
|
|
16409
17551
|
if (!RUN_STATUSES.has(item.status) || item.outcome !== null && !RUN_OUTCOMES.has(item.outcome) || typeof item.lateReport !== "boolean") throw new Error("automation-response-invalid");
|
|
16410
17552
|
return {
|
|
16411
|
-
id: stringValue(item.id),
|
|
16412
|
-
automationId: stringValue(item.automationId),
|
|
16413
|
-
generation: integer(item.generation, 1),
|
|
16414
|
-
scheduledFor: integer(item.scheduledFor),
|
|
16415
|
-
machineId: stringValue(item.machineId),
|
|
17553
|
+
id: stringValue$1(item.id),
|
|
17554
|
+
automationId: stringValue$1(item.automationId),
|
|
17555
|
+
generation: integer$1(item.generation, 1),
|
|
17556
|
+
scheduledFor: integer$1(item.scheduledFor),
|
|
17557
|
+
machineId: stringValue$1(item.machineId),
|
|
16416
17558
|
status: item.status,
|
|
16417
17559
|
sessionId: nullableString(item.sessionId),
|
|
16418
17560
|
outcome: item.outcome,
|
|
16419
17561
|
detailCiphertext: nullableString(item.detailCiphertext),
|
|
16420
|
-
claimedAt: integer(item.claimedAt),
|
|
17562
|
+
claimedAt: integer$1(item.claimedAt),
|
|
16421
17563
|
startedAt: nullableInteger(item.startedAt),
|
|
16422
17564
|
completedAt: nullableInteger(item.completedAt),
|
|
16423
17565
|
lateReport: item.lateReport,
|
|
@@ -16455,6 +17597,98 @@ function parseServerGithubWebhookStatus(value2) {
|
|
|
16455
17597
|
lastDeliveryAt: nullableTimestamp(item.lastDeliveryAt)
|
|
16456
17598
|
};
|
|
16457
17599
|
}
|
|
17600
|
+
const SESSION_FOLLOWUP_WIRE_VERSION = 1;
|
|
17601
|
+
const STATUSES$1 = /* @__PURE__ */ new Set(["WAITING", "DELIVERY_PENDING", "PAUSED", "COMPLETED", "CANCELLED", "FAILED"]);
|
|
17602
|
+
const TERMINAL_CODES = /* @__PURE__ */ new Set([
|
|
17603
|
+
"CLEAN",
|
|
17604
|
+
"LOW_OR_NIT_ONLY",
|
|
17605
|
+
"UNSTRUCTURED",
|
|
17606
|
+
"ROUNDS_EXHAUSTED",
|
|
17607
|
+
"USER_INTERVENTION",
|
|
17608
|
+
"STOPPED",
|
|
17609
|
+
"SESSION_UNAVAILABLE",
|
|
17610
|
+
"TARGET_MISMATCH",
|
|
17611
|
+
"DECRYPT_FAILED",
|
|
17612
|
+
"PERMISSION_REVOKED"
|
|
17613
|
+
]);
|
|
17614
|
+
function invalid() {
|
|
17615
|
+
throw new Error("session-followup-response-invalid");
|
|
17616
|
+
}
|
|
17617
|
+
function record$1(value2) {
|
|
17618
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) invalid();
|
|
17619
|
+
return value2;
|
|
17620
|
+
}
|
|
17621
|
+
function stringValue(value2) {
|
|
17622
|
+
return typeof value2 === "string" && value2 ? value2 : invalid();
|
|
17623
|
+
}
|
|
17624
|
+
function boundedString(value2, max) {
|
|
17625
|
+
const parsed = stringValue(value2);
|
|
17626
|
+
return parsed.length <= max ? parsed : invalid();
|
|
17627
|
+
}
|
|
17628
|
+
function integer(value2, min = 0) {
|
|
17629
|
+
return Number.isSafeInteger(value2) && value2 >= min ? value2 : invalid();
|
|
17630
|
+
}
|
|
17631
|
+
function validateSessionFollowupCreateInput(input) {
|
|
17632
|
+
try {
|
|
17633
|
+
if (input.wireVersion !== SESSION_FOLLOWUP_WIRE_VERSION || input.payloadVersion !== 1) invalid();
|
|
17634
|
+
if (!input.sessionId || input.sessionId.length > 200 || !input.viewerKeyId || input.viewerKeyId.length > 128) invalid();
|
|
17635
|
+
const totalRounds = integer(input.totalRounds, 2);
|
|
17636
|
+
const currentRound = integer(input.currentRound, 1);
|
|
17637
|
+
if (totalRounds > 7 || currentRound > totalRounds) invalid();
|
|
17638
|
+
integer(input.responseBoundarySeq);
|
|
17639
|
+
integer(input.viewerKeyVersion, 1);
|
|
17640
|
+
integer(input.machineKeyVersion, 1);
|
|
17641
|
+
versionedAutomationBase64(input.payloadCiphertext, 128 * 1024);
|
|
17642
|
+
versionedAutomationBase64(input.viewerKeyEnvelope, 105, 105);
|
|
17643
|
+
versionedAutomationBase64(input.machineKeyEnvelope, 105, 105);
|
|
17644
|
+
return input;
|
|
17645
|
+
} catch {
|
|
17646
|
+
throw new Error("session-followup-request-invalid");
|
|
17647
|
+
}
|
|
17648
|
+
}
|
|
17649
|
+
function parseSessionFollowupPublic(value2) {
|
|
17650
|
+
try {
|
|
17651
|
+
const item = record$1(value2);
|
|
17652
|
+
if (!STATUSES$1.has(item.status)) invalid();
|
|
17653
|
+
if (item.terminalCode !== null && !TERMINAL_CODES.has(item.terminalCode)) invalid();
|
|
17654
|
+
if (item.payloadVersion !== 1) invalid();
|
|
17655
|
+
const totalRounds = integer(item.totalRounds, 2);
|
|
17656
|
+
const currentRound = integer(item.currentRound, 1);
|
|
17657
|
+
if (totalRounds > 7 || currentRound > totalRounds) invalid();
|
|
17658
|
+
return {
|
|
17659
|
+
id: stringValue(item.id),
|
|
17660
|
+
projectId: stringValue(item.projectId),
|
|
17661
|
+
ownerAccountId: stringValue(item.ownerAccountId),
|
|
17662
|
+
machineAccountId: stringValue(item.machineAccountId),
|
|
17663
|
+
machineId: stringValue(item.machineId),
|
|
17664
|
+
sessionId: stringValue(item.sessionId),
|
|
17665
|
+
revision: integer(item.revision, 1),
|
|
17666
|
+
generation: integer(item.generation, 1),
|
|
17667
|
+
step: integer(item.step, 1),
|
|
17668
|
+
status: item.status,
|
|
17669
|
+
terminalCode: item.terminalCode,
|
|
17670
|
+
totalRounds,
|
|
17671
|
+
currentRound,
|
|
17672
|
+
responseBoundarySeq: integer(item.responseBoundarySeq),
|
|
17673
|
+
lastObservedSeq: integer(item.lastObservedSeq),
|
|
17674
|
+
payloadVersion: 1,
|
|
17675
|
+
payloadCiphertext: versionedAutomationBase64(item.payloadCiphertext, 128 * 1024),
|
|
17676
|
+
viewerKeyId: boundedString(item.viewerKeyId, 128),
|
|
17677
|
+
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
17678
|
+
viewerKeyEnvelope: versionedAutomationBase64(item.viewerKeyEnvelope, 105, 105),
|
|
17679
|
+
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17680
|
+
completedAt: item.completedAt === null ? null : integer(item.completedAt),
|
|
17681
|
+
createdAt: integer(item.createdAt),
|
|
17682
|
+
updatedAt: integer(item.updatedAt)
|
|
17683
|
+
};
|
|
17684
|
+
} catch {
|
|
17685
|
+
throw new Error("session-followup-response-invalid");
|
|
17686
|
+
}
|
|
17687
|
+
}
|
|
17688
|
+
function parseSessionFollowupList(value2) {
|
|
17689
|
+
if (!Array.isArray(value2)) throw new Error("session-followup-response-invalid");
|
|
17690
|
+
return value2.map(parseSessionFollowupPublic);
|
|
17691
|
+
}
|
|
16458
17692
|
const ERROR_CODES = /* @__PURE__ */ new Set([
|
|
16459
17693
|
"PAIRING_INVALID",
|
|
16460
17694
|
"PAIRING_EXPIRED",
|
|
@@ -16486,18 +17720,18 @@ function readCloudMobileLoginPairingErrorCode(body) {
|
|
|
16486
17720
|
const PAIRING_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16487
17721
|
const CODE_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
16488
17722
|
const STATUSES = /* @__PURE__ */ new Set(["pending", "consumed", "revoked", "expired"]);
|
|
16489
|
-
function invalidResponse() {
|
|
17723
|
+
function invalidResponse$1() {
|
|
16490
17724
|
throw new Error("PAIRING_RESPONSE_INVALID");
|
|
16491
17725
|
}
|
|
16492
17726
|
function parseCloudMobileLoginPairing(raw, options = {}) {
|
|
16493
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) invalidResponse();
|
|
17727
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) invalidResponse$1();
|
|
16494
17728
|
const value2 = raw;
|
|
16495
|
-
if (typeof value2.pairingId !== "string" || !PAIRING_ID_PATTERN.test(value2.pairingId)) invalidResponse();
|
|
16496
|
-
if (!Number.isSafeInteger(value2.expiresAt) || value2.expiresAt <= 0) invalidResponse();
|
|
16497
|
-
if (!Number.isSafeInteger(value2.serverTime) || value2.serverTime <= 0) invalidResponse();
|
|
16498
|
-
if (typeof value2.status !== "string" || !STATUSES.has(value2.status)) invalidResponse();
|
|
16499
|
-
if (options.requireCode && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse();
|
|
16500
|
-
if (value2.code !== void 0 && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse();
|
|
17729
|
+
if (typeof value2.pairingId !== "string" || !PAIRING_ID_PATTERN.test(value2.pairingId)) invalidResponse$1();
|
|
17730
|
+
if (!Number.isSafeInteger(value2.expiresAt) || value2.expiresAt <= 0) invalidResponse$1();
|
|
17731
|
+
if (!Number.isSafeInteger(value2.serverTime) || value2.serverTime <= 0) invalidResponse$1();
|
|
17732
|
+
if (typeof value2.status !== "string" || !STATUSES.has(value2.status)) invalidResponse$1();
|
|
17733
|
+
if (options.requireCode && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse$1();
|
|
17734
|
+
if (value2.code !== void 0 && (typeof value2.code !== "string" || !CODE_PATTERN.test(value2.code))) invalidResponse$1();
|
|
16501
17735
|
return {
|
|
16502
17736
|
pairingId: value2.pairingId,
|
|
16503
17737
|
expiresAt: value2.expiresAt,
|
|
@@ -16506,6 +17740,43 @@ function parseCloudMobileLoginPairing(raw, options = {}) {
|
|
|
16506
17740
|
...typeof value2.code === "string" ? { code: value2.code } : {}
|
|
16507
17741
|
};
|
|
16508
17742
|
}
|
|
17743
|
+
const BROWSER_VIEWER_OPEN_TIMEOUT_MS = 13e4;
|
|
17744
|
+
const BROWSER_VIEWER_INSTALL_TIMEOUT_MS = 31e4;
|
|
17745
|
+
function invalidResponse() {
|
|
17746
|
+
throw new Error("BROWSER_VIEWER_RESPONSE_INVALID");
|
|
17747
|
+
}
|
|
17748
|
+
function responseRecord(value2) {
|
|
17749
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) invalidResponse();
|
|
17750
|
+
return value2;
|
|
17751
|
+
}
|
|
17752
|
+
function parseBrowserViewerOpenResponse(value2) {
|
|
17753
|
+
const response = responseRecord(value2);
|
|
17754
|
+
if (typeof response.token !== "string" || response.token.length === 0 || typeof response.relayUrl !== "string" && response.relayUrl !== null || response.routingVersion !== 1 || typeof response.previewDomain !== "string" && response.previewDomain !== null || !Number.isInteger(response.webPort) || response.webPort < 1 || response.webPort > 65535 || typeof response.bridgeReady !== "boolean") invalidResponse();
|
|
17755
|
+
return {
|
|
17756
|
+
token: response.token,
|
|
17757
|
+
relayUrl: response.relayUrl,
|
|
17758
|
+
routingVersion: 1,
|
|
17759
|
+
previewDomain: response.previewDomain,
|
|
17760
|
+
webPort: response.webPort,
|
|
17761
|
+
bridgeReady: response.bridgeReady
|
|
17762
|
+
};
|
|
17763
|
+
}
|
|
17764
|
+
function parseBrowserViewerInstallResponse(value2) {
|
|
17765
|
+
const response = responseRecord(value2);
|
|
17766
|
+
if (response.action === "already-installed") return { kind: "already-installed" };
|
|
17767
|
+
if (response.action === "run") {
|
|
17768
|
+
if (response.ok !== true) invalidResponse();
|
|
17769
|
+
return { kind: "installed" };
|
|
17770
|
+
}
|
|
17771
|
+
if (response.action === "manual") {
|
|
17772
|
+
if (typeof response.command !== "string" || typeof response.reason !== "string") invalidResponse();
|
|
17773
|
+
return {
|
|
17774
|
+
kind: "manual-required",
|
|
17775
|
+
reason: response.command.length === 0 ? "unsupported-platform" : "admin-required"
|
|
17776
|
+
};
|
|
17777
|
+
}
|
|
17778
|
+
return invalidResponse();
|
|
17779
|
+
}
|
|
16509
17780
|
const FIXED_API_BASE_URL = "https://saycode.ai";
|
|
16510
17781
|
const FIXED_SYNC_BASE_URL = "https://saycode.ai";
|
|
16511
17782
|
const machineRegistryCache = /* @__PURE__ */ new Map();
|
|
@@ -16537,6 +17808,20 @@ class MachineDeleteConflictError extends Error {
|
|
|
16537
17808
|
const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
16538
17809
|
const LONG_REQUEST_TIMEOUT_MS = 18e4;
|
|
16539
17810
|
const UPLOAD_REQUEST_TIMEOUT_MS = 12e4;
|
|
17811
|
+
const OWNER_SYNC_WARNING_MAX_COUNT = 16;
|
|
17812
|
+
const OWNER_SYNC_WARNING_MAX_LENGTH = 100;
|
|
17813
|
+
function parseOwnerSyncWarnings(value2) {
|
|
17814
|
+
if (!Array.isArray(value2)) return [];
|
|
17815
|
+
const warnings2 = [];
|
|
17816
|
+
for (const item of value2) {
|
|
17817
|
+
if (typeof item !== "string") continue;
|
|
17818
|
+
const warning = item.trim();
|
|
17819
|
+
if (!warning || warning.length > OWNER_SYNC_WARNING_MAX_LENGTH) continue;
|
|
17820
|
+
warnings2.push(warning);
|
|
17821
|
+
if (warnings2.length === OWNER_SYNC_WARNING_MAX_COUNT) break;
|
|
17822
|
+
}
|
|
17823
|
+
return warnings2;
|
|
17824
|
+
}
|
|
16540
17825
|
function isRequestTimeoutError(error) {
|
|
16541
17826
|
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
16542
17827
|
}
|
|
@@ -16546,7 +17831,9 @@ async function fetchWithTimeout(url2, init, timeoutMs) {
|
|
|
16546
17831
|
return await fetch(url2, requestAbort.signal ? { ...init, signal: requestAbort.signal } : init);
|
|
16547
17832
|
} catch (error) {
|
|
16548
17833
|
if (requestAbort.didTimeout() && isRequestTimeoutError(error)) {
|
|
16549
|
-
|
|
17834
|
+
const timeout = new Error(translateCurrent("api.serverTimeout", { seconds: Math.round(timeoutMs / 1e3) }));
|
|
17835
|
+
timeout.name = "ServerTimeoutError";
|
|
17836
|
+
throw timeout;
|
|
16550
17837
|
}
|
|
16551
17838
|
throw error;
|
|
16552
17839
|
}
|
|
@@ -16635,7 +17922,8 @@ function normalizeGithubPullRequest(raw) {
|
|
|
16635
17922
|
head: typeof head === "string" ? head : null,
|
|
16636
17923
|
base: typeof base === "string" ? base : null,
|
|
16637
17924
|
draft: raw.draft === true,
|
|
16638
|
-
merged
|
|
17925
|
+
merged,
|
|
17926
|
+
...raw.autoMerge === true ? { autoMerge: true } : {}
|
|
16639
17927
|
};
|
|
16640
17928
|
}
|
|
16641
17929
|
function extractPullRequestList(raw) {
|
|
@@ -16671,6 +17959,7 @@ function normalizeMachine(row2, candidateKeys) {
|
|
|
16671
17959
|
const host = row2.host || registry?.host || (typeof decrypted.host === "string" ? decrypted.host : row2.id.slice(0, 8));
|
|
16672
17960
|
const platform2 = row2.platform || registry?.platform || (typeof decrypted.platform === "string" ? decrypted.platform : "unknown");
|
|
16673
17961
|
const automationSupport = normalizeAutomationSupport(decrypted.automationSupport) ?? normalizeAutomationSupport(registry?.automationSupport);
|
|
17962
|
+
const autonomousQualityGate = decrypted.autonomousQualityGateSupport ?? registry?.autonomousQualityGateSupport;
|
|
16674
17963
|
return {
|
|
16675
17964
|
id: row2.id,
|
|
16676
17965
|
accountId: row2.accountId,
|
|
@@ -16683,9 +17972,12 @@ function normalizeMachine(row2, candidateKeys) {
|
|
|
16683
17972
|
happyCliVersion: row2.happyCliVersion || registry?.happyCliVersion || (typeof decrypted.happyCliVersion === "string" ? decrypted.happyCliVersion : void 0),
|
|
16684
17973
|
homeDir: row2.homeDir || registry?.homeDir || (typeof decrypted.homeDir === "string" ? decrypted.homeDir : void 0),
|
|
16685
17974
|
resources,
|
|
17975
|
+
daemonRunning: readDaemonRunning(daemonState),
|
|
16686
17976
|
mcpCallerGrantPublicKey: readMcpCallerGrantPublicKey(daemonState),
|
|
16687
17977
|
automationSupport,
|
|
17978
|
+
autonomousQualityGate,
|
|
16688
17979
|
filesystemScope: decrypted.filesystemScope,
|
|
17980
|
+
additionalDirectories: decrypted.additionalDirectories,
|
|
16689
17981
|
registry
|
|
16690
17982
|
};
|
|
16691
17983
|
}
|
|
@@ -16713,7 +18005,56 @@ class ApiClient {
|
|
|
16713
18005
|
headers: { "Content-Type": "application/json" },
|
|
16714
18006
|
body: JSON.stringify(input)
|
|
16715
18007
|
}, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
16716
|
-
return this.handleResponse(response);
|
|
18008
|
+
return parsePasswordLoginResponse(await this.handleResponse(response));
|
|
18009
|
+
}
|
|
18010
|
+
/**
|
|
18011
|
+
* MFA(TOTP) 로그인 2단계 — 서버 mfaLoginRoutes `/api/auth/mfa/login/totp`.
|
|
18012
|
+
* 성공 응답은 일반 로그인 결과의 상위집합이라 같은 strict parser 를 쓴다.
|
|
18013
|
+
* 실패는 handleResponse 의 ApiError(status + body 의 MFA_* code)로 온다.
|
|
18014
|
+
*/
|
|
18015
|
+
async verifyMfaTotpLogin(input) {
|
|
18016
|
+
const response = await fetchWithTimeout(absoluteUrl(this.config.apiBaseUrl, "/api/auth/mfa/login/totp"), {
|
|
18017
|
+
method: "POST",
|
|
18018
|
+
headers: { "Content-Type": "application/json" },
|
|
18019
|
+
body: JSON.stringify(input)
|
|
18020
|
+
}, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
18021
|
+
return parseCloudLoginResult(await this.handleResponse(response));
|
|
18022
|
+
}
|
|
18023
|
+
async startMfaPasskeyHandoff(input) {
|
|
18024
|
+
const response = await fetchWithTimeout(
|
|
18025
|
+
absoluteUrl(this.config.apiBaseUrl, "/api/auth/mfa/login/passkey-handoffs"),
|
|
18026
|
+
{
|
|
18027
|
+
method: "POST",
|
|
18028
|
+
headers: { "Content-Type": "application/json" },
|
|
18029
|
+
body: JSON.stringify({
|
|
18030
|
+
version: 1,
|
|
18031
|
+
loginToken: input.loginToken,
|
|
18032
|
+
clientKind: input.clientKind,
|
|
18033
|
+
...input.deviceId ? { deviceId: input.deviceId } : {},
|
|
18034
|
+
e2eeDeviceApprovalVersion: 1
|
|
18035
|
+
}),
|
|
18036
|
+
signal: input.signal
|
|
18037
|
+
},
|
|
18038
|
+
DEFAULT_REQUEST_TIMEOUT_MS
|
|
18039
|
+
);
|
|
18040
|
+
return parseMfaPasskeyHandoff(await this.handleResponse(response), this.config.apiBaseUrl);
|
|
18041
|
+
}
|
|
18042
|
+
async exchangeMfaPasskeyHandoff(input) {
|
|
18043
|
+
const response = await fetchWithTimeout(
|
|
18044
|
+
absoluteUrl(
|
|
18045
|
+
this.config.apiBaseUrl,
|
|
18046
|
+
`/api/auth/mfa/login/passkey-handoffs/${encodeURIComponent(input.handoffId)}/exchange`
|
|
18047
|
+
),
|
|
18048
|
+
{
|
|
18049
|
+
method: "POST",
|
|
18050
|
+
headers: { "Content-Type": "application/json" },
|
|
18051
|
+
body: JSON.stringify({ version: 1, exchangeToken: input.exchangeToken }),
|
|
18052
|
+
signal: input.signal
|
|
18053
|
+
},
|
|
18054
|
+
DEFAULT_REQUEST_TIMEOUT_MS
|
|
18055
|
+
);
|
|
18056
|
+
const data = await this.handleResponse(response);
|
|
18057
|
+
return response.status === 202 ? parsePendingMfaPasskeyExchange(data) : { kind: "complete", result: parseCloudLoginResult(data) };
|
|
16717
18058
|
}
|
|
16718
18059
|
async getMe() {
|
|
16719
18060
|
return this.request("/api/auth/me", { boundary: "api" });
|
|
@@ -17011,6 +18352,17 @@ class ApiClient {
|
|
|
17011
18352
|
);
|
|
17012
18353
|
return Array.isArray(data) ? data : data.members ?? [];
|
|
17013
18354
|
}
|
|
18355
|
+
/**
|
|
18356
|
+
* 회사 공유 키 공개키 + 자기 grant 조회 (specs/e2ee-company-key-activation
|
|
18357
|
+
* P1). 응답 해석(myGrants null=미수립 vs []=권한없음)은 companyKeyChain 이
|
|
18358
|
+
* 담당하므로 여기서는 원본을 그대로 넘긴다.
|
|
18359
|
+
*/
|
|
18360
|
+
async fetchCompanyDataKey(companyId) {
|
|
18361
|
+
return this.request(
|
|
18362
|
+
`/api/admin/companies/${encodeURIComponent(companyId)}/data-key`,
|
|
18363
|
+
{ boundary: "api" }
|
|
18364
|
+
);
|
|
18365
|
+
}
|
|
17014
18366
|
async updateCompanyMemberRole(companyId, accountId, role) {
|
|
17015
18367
|
await this.request(`/api/admin/companies/${encodeURIComponent(companyId)}/members/${encodeURIComponent(accountId)}`, {
|
|
17016
18368
|
method: "PATCH",
|
|
@@ -17149,6 +18501,10 @@ class ApiClient {
|
|
|
17149
18501
|
}
|
|
17150
18502
|
);
|
|
17151
18503
|
}
|
|
18504
|
+
/** specs/org-elasticsearch-search — 경계 어댑터는 src/api/orgSearch.ts 가 소유한다. */
|
|
18505
|
+
orgSearch() {
|
|
18506
|
+
return createOrgSearchApi({ request: (path, init) => this.request(path, { boundary: "api", ...init ?? {} }) });
|
|
18507
|
+
}
|
|
17152
18508
|
async getCompanyHappyCredentials(companyId) {
|
|
17153
18509
|
return this.request(`/api/companies/${encodeURIComponent(companyId)}/happy-credentials`, { boundary: "api" });
|
|
17154
18510
|
}
|
|
@@ -17285,6 +18641,45 @@ class ApiClient {
|
|
|
17285
18641
|
const data = await this.request(`/v1/projects/${encodeURIComponent(projectId)}/automation-target`, { boundary: "sync" });
|
|
17286
18642
|
return parseServerAutomationTarget(data.target);
|
|
17287
18643
|
}
|
|
18644
|
+
sessionFollowupPath(projectId, followupId) {
|
|
18645
|
+
const root = `/v1/projects/${encodeURIComponent(projectId)}/session-followups`;
|
|
18646
|
+
return followupId ? `${root}/${encodeURIComponent(followupId)}` : root;
|
|
18647
|
+
}
|
|
18648
|
+
async listSessionFollowups(projectId, sessionId) {
|
|
18649
|
+
const query = sessionId ? `?session_id=${encodeURIComponent(sessionId)}&limit=100` : "?limit=100";
|
|
18650
|
+
return parseSessionFollowupList(await this.request(`${this.sessionFollowupPath(projectId)}${query}`, { boundary: "sync" }));
|
|
18651
|
+
}
|
|
18652
|
+
async createSessionFollowup(projectId, input) {
|
|
18653
|
+
return parseSessionFollowupPublic(await this.request(this.sessionFollowupPath(projectId), {
|
|
18654
|
+
method: "POST",
|
|
18655
|
+
boundary: "sync",
|
|
18656
|
+
body: JSON.stringify(validateSessionFollowupCreateInput(input))
|
|
18657
|
+
}));
|
|
18658
|
+
}
|
|
18659
|
+
async mutateSessionFollowupRevision(input) {
|
|
18660
|
+
const item = this.sessionFollowupPath(input.projectId, input.followupId);
|
|
18661
|
+
try {
|
|
18662
|
+
return parseSessionFollowupPublic(await this.request(input.action ? `${item}/${input.action}` : item, {
|
|
18663
|
+
method: input.method,
|
|
18664
|
+
boundary: "sync",
|
|
18665
|
+
body: JSON.stringify({ wireVersion: SESSION_FOLLOWUP_WIRE_VERSION, expectedRevision: input.expectedRevision })
|
|
18666
|
+
}));
|
|
18667
|
+
} catch (error) {
|
|
18668
|
+
if (!(error instanceof ApiError) || error.status !== 409) throw error;
|
|
18669
|
+
try {
|
|
18670
|
+
const body = JSON.parse(error.body);
|
|
18671
|
+
return parseSessionFollowupPublic(body.latest);
|
|
18672
|
+
} catch {
|
|
18673
|
+
throw error;
|
|
18674
|
+
}
|
|
18675
|
+
}
|
|
18676
|
+
}
|
|
18677
|
+
async stopSessionFollowup(projectId, followupId, expectedRevision) {
|
|
18678
|
+
return this.mutateSessionFollowupRevision({ projectId, followupId, expectedRevision, method: "POST", action: "stop" });
|
|
18679
|
+
}
|
|
18680
|
+
async deleteSessionFollowup(projectId, followupId, expectedRevision) {
|
|
18681
|
+
return this.mutateSessionFollowupRevision({ projectId, followupId, expectedRevision, method: "DELETE" });
|
|
18682
|
+
}
|
|
17288
18683
|
async setServerAutomationViewerKey(projectId, input) {
|
|
17289
18684
|
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/automation-viewer-key`, {
|
|
17290
18685
|
method: "PUT",
|
|
@@ -17952,23 +19347,28 @@ class ApiClient {
|
|
|
17952
19347
|
boundary: "api"
|
|
17953
19348
|
});
|
|
17954
19349
|
}
|
|
17955
|
-
async fetchSessions(candidateKeys = [], tokenOverride) {
|
|
19350
|
+
async fetchSessions(candidateKeys = [], tokenOverride, accountPrivateKey = null) {
|
|
19351
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
17956
19352
|
const data = await this.request("/v1/sessions", {
|
|
17957
19353
|
boundary: "sync",
|
|
17958
19354
|
tokenOverride
|
|
17959
19355
|
});
|
|
17960
19356
|
const rows = Array.isArray(data) ? data : data.sessions ?? [];
|
|
19357
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
17961
19358
|
return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
|
|
17962
19359
|
}
|
|
17963
|
-
async fetchSessionsPage(candidateKeys = [], cursor, limit = 200, tokenOverride) {
|
|
19360
|
+
async fetchSessionsPage(candidateKeys = [], cursor, limit = 200, tokenOverride, accountPrivateKey = null) {
|
|
17964
19361
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
17965
19362
|
if (cursor) params.set("cursor", cursor);
|
|
19363
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
17966
19364
|
const data = await this.request(
|
|
17967
19365
|
`/v2/sessions?${params.toString()}`,
|
|
17968
19366
|
{ boundary: "sync", tokenOverride }
|
|
17969
19367
|
);
|
|
19368
|
+
const rows = data.sessions ?? [];
|
|
19369
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
17970
19370
|
return {
|
|
17971
|
-
sessions:
|
|
19371
|
+
sessions: rows.map((row2) => apiSessionToSession(row2, candidateKeys)),
|
|
17972
19372
|
nextCursor: data.nextCursor ?? null,
|
|
17973
19373
|
hasNext: data.hasNext === true
|
|
17974
19374
|
};
|
|
@@ -17983,8 +19383,11 @@ class ApiClient {
|
|
|
17983
19383
|
});
|
|
17984
19384
|
return data.sessions ?? [];
|
|
17985
19385
|
}
|
|
17986
|
-
async lookupSessions(candidateKeys, ids, tokenOverride) {
|
|
17987
|
-
|
|
19386
|
+
async lookupSessions(candidateKeys, ids, tokenOverride, accountPrivateKey = null) {
|
|
19387
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
19388
|
+
const rows = await this.lookupApiSessions(ids, tokenOverride);
|
|
19389
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
19390
|
+
return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
|
|
17988
19391
|
}
|
|
17989
19392
|
async deleteSession(sessionId, tokenOverride) {
|
|
17990
19393
|
await this.request(`/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
@@ -18107,6 +19510,22 @@ class ApiClient {
|
|
|
18107
19510
|
message: typeof raw.message === "string" ? raw.message : null
|
|
18108
19511
|
};
|
|
18109
19512
|
}
|
|
19513
|
+
/** specs/desktop-pr-auto-merge-toggle — aplus-dev-studio#3315 server endpoint. */
|
|
19514
|
+
async setGithubPullRequestAutoMerge(input, tokenOverride) {
|
|
19515
|
+
const raw = await this.request(
|
|
19516
|
+
`/api/projects/${encodeURIComponent(input.projectId)}/pull-requests/${encodeURIComponent(String(input.number))}/auto-merge`,
|
|
19517
|
+
{
|
|
19518
|
+
method: "POST",
|
|
19519
|
+
boundary: "api",
|
|
19520
|
+
tokenOverride,
|
|
19521
|
+
body: JSON.stringify({ enabled: input.enabled, ...input.method ? { method: input.method } : {} })
|
|
19522
|
+
}
|
|
19523
|
+
);
|
|
19524
|
+
return {
|
|
19525
|
+
autoMerge: raw.autoMerge === true,
|
|
19526
|
+
prNumber: typeof raw.prNumber === "number" ? raw.prNumber : input.number
|
|
19527
|
+
};
|
|
19528
|
+
}
|
|
18110
19529
|
async fetchMessages(sessionId, afterSeq, tokenOverride, signal) {
|
|
18111
19530
|
const params = new URLSearchParams({ limit: "100" });
|
|
18112
19531
|
if (afterSeq !== void 0) params.set("after_seq", String(afterSeq));
|
|
@@ -18169,9 +19588,8 @@ class ApiClient {
|
|
|
18169
19588
|
}
|
|
18170
19589
|
async sendUserTextMessage(input) {
|
|
18171
19590
|
const localId = input.localId || `desktop-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
18172
|
-
const key = input.secretBase64 ? decodeBase64(input.secretBase64) : null;
|
|
18173
19591
|
const content = buildUserTextMessageContent(input);
|
|
18174
|
-
const encoded = encodeMessageContent(content,
|
|
19592
|
+
const encoded = input.dataEncryptionKey ? await encryptDataKeyContent(input.dataEncryptionKey, content) : encodeMessageContent(content, input.secretBase64 ? decodeBase64(input.secretBase64) : null);
|
|
18175
19593
|
const result = await this.sendMessages(input.sessionId, [{ localId, content: encoded }], input.tokenOverride);
|
|
18176
19594
|
return { localId, sentSeq: result.messages?.[0]?.seq };
|
|
18177
19595
|
}
|
|
@@ -18243,6 +19661,34 @@ class ApiClient {
|
|
|
18243
19661
|
}
|
|
18244
19662
|
return resolvedProjectPreviewRelayUrl({ ...input, response: { ...response, token: response.token }, syncBaseUrl: this.config.syncBaseUrl });
|
|
18245
19663
|
}
|
|
19664
|
+
async openBrowserViewer(machineId, signal) {
|
|
19665
|
+
return parseBrowserViewerOpenResponse(await this.request(
|
|
19666
|
+
`/api/machines/${encodeURIComponent(machineId)}/browser-viewer/open`,
|
|
19667
|
+
{
|
|
19668
|
+
method: "POST",
|
|
19669
|
+
boundary: "api",
|
|
19670
|
+
signal,
|
|
19671
|
+
timeoutMs: BROWSER_VIEWER_OPEN_TIMEOUT_MS
|
|
19672
|
+
}
|
|
19673
|
+
));
|
|
19674
|
+
}
|
|
19675
|
+
async installBrowserViewer(machineId, signal) {
|
|
19676
|
+
return this.installBrowserViewerComponent(machineId, "install", signal);
|
|
19677
|
+
}
|
|
19678
|
+
async installBrowserViewerChrome(machineId, signal) {
|
|
19679
|
+
return this.installBrowserViewerComponent(machineId, "install-chrome", signal);
|
|
19680
|
+
}
|
|
19681
|
+
async installBrowserViewerComponent(machineId, action, signal) {
|
|
19682
|
+
return parseBrowserViewerInstallResponse(await this.request(
|
|
19683
|
+
`/api/machines/${encodeURIComponent(machineId)}/browser-viewer/${action}`,
|
|
19684
|
+
{
|
|
19685
|
+
method: "POST",
|
|
19686
|
+
boundary: "api",
|
|
19687
|
+
signal,
|
|
19688
|
+
timeoutMs: BROWSER_VIEWER_INSTALL_TIMEOUT_MS
|
|
19689
|
+
}
|
|
19690
|
+
));
|
|
19691
|
+
}
|
|
18246
19692
|
// --- Project deploy & share (specs/desktop-project-deploy-share) ---
|
|
18247
19693
|
async moveProject(projectId, input) {
|
|
18248
19694
|
return this.request(`/api/projects/${encodeURIComponent(projectId)}/move`, {
|
|
@@ -18252,15 +19698,19 @@ class ApiClient {
|
|
|
18252
19698
|
});
|
|
18253
19699
|
}
|
|
18254
19700
|
async syncOwnerWorkspace(projectId, input = {}) {
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
19701
|
+
const data = await this.request(
|
|
19702
|
+
`/api/projects/${encodeURIComponent(projectId)}/sync-owner-workspace`,
|
|
19703
|
+
{
|
|
19704
|
+
method: "POST",
|
|
19705
|
+
boundary: "api",
|
|
19706
|
+
timeoutMs: LONG_REQUEST_TIMEOUT_MS,
|
|
19707
|
+
body: JSON.stringify({
|
|
19708
|
+
trigger: input.trigger ?? "owner-manual",
|
|
19709
|
+
...input.vcsTarget ? { vcsTarget: input.vcsTarget } : {}
|
|
19710
|
+
})
|
|
19711
|
+
}
|
|
19712
|
+
);
|
|
19713
|
+
return { result: data.result, warnings: parseOwnerSyncWarnings(data.warnings) };
|
|
18264
19714
|
}
|
|
18265
19715
|
async getProjectVcsRemote(projectId) {
|
|
18266
19716
|
const data = await this.request(
|
|
@@ -18364,6 +19814,30 @@ class ApiClient {
|
|
|
18364
19814
|
timeoutMs: LONG_REQUEST_TIMEOUT_MS
|
|
18365
19815
|
});
|
|
18366
19816
|
}
|
|
19817
|
+
async publishStaticArtifact(source, signal) {
|
|
19818
|
+
const { version: _version, ...serverSource } = source;
|
|
19819
|
+
const response = await this.request("/api/artifact-publications", {
|
|
19820
|
+
method: "POST",
|
|
19821
|
+
boundary: "api",
|
|
19822
|
+
body: JSON.stringify({ source: serverSource }),
|
|
19823
|
+
signal
|
|
19824
|
+
});
|
|
19825
|
+
return parseStaticArtifactPublicationResult(response, {
|
|
19826
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
19827
|
+
operation: "publish",
|
|
19828
|
+
source
|
|
19829
|
+
});
|
|
19830
|
+
}
|
|
19831
|
+
async revokeStaticArtifactPublication(publicationId, signal) {
|
|
19832
|
+
const response = await this.request(
|
|
19833
|
+
`/api/artifact-publications/${encodeURIComponent(publicationId)}`,
|
|
19834
|
+
{ method: "DELETE", boundary: "api", signal }
|
|
19835
|
+
);
|
|
19836
|
+
return parseStaticArtifactPublicationResult(response, {
|
|
19837
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
19838
|
+
operation: "revoke"
|
|
19839
|
+
});
|
|
19840
|
+
}
|
|
18367
19841
|
async getDeployGitHubFiles(projectId, branch = "main") {
|
|
18368
19842
|
const data = await this.request("/deploy/github-files", {
|
|
18369
19843
|
method: "POST",
|
|
@@ -18812,8 +20286,8 @@ function happyHomeOrder(input) {
|
|
|
18812
20286
|
if (!runtime) return [...homes];
|
|
18813
20287
|
return [runtime, ...homes.filter((source) => source !== runtime)];
|
|
18814
20288
|
}
|
|
18815
|
-
function selectFirstValidCredential(
|
|
18816
|
-
for (const candidate of
|
|
20289
|
+
function selectFirstValidCredential(candidates2, readFile2) {
|
|
20290
|
+
for (const candidate of candidates2) {
|
|
18817
20291
|
const raw = readFile2(candidate.path);
|
|
18818
20292
|
if (raw === null) continue;
|
|
18819
20293
|
const credential = parseAccessKeyFile(raw);
|
|
@@ -18969,7 +20443,7 @@ class RealtimeSocket {
|
|
|
18969
20443
|
}
|
|
18970
20444
|
const schedule = connectSchedule(totalTimeoutMs);
|
|
18971
20445
|
let lastError = null;
|
|
18972
|
-
for (let
|
|
20446
|
+
for (let attempt2 = 0; attempt2 < CONNECT_ATTEMPTS; attempt2 += 1) {
|
|
18973
20447
|
const result = await this.connectOnce(schedule.attemptTimeoutMs);
|
|
18974
20448
|
if (result.type === "cancelled") throw this.cancelledError();
|
|
18975
20449
|
if (result.type === "connected") {
|
|
@@ -18979,7 +20453,7 @@ class RealtimeSocket {
|
|
|
18979
20453
|
}
|
|
18980
20454
|
if (result.type === "error") lastError = result.error;
|
|
18981
20455
|
this.socket.disconnect();
|
|
18982
|
-
const retryDelay = schedule.retryDelaysMs[
|
|
20456
|
+
const retryDelay = schedule.retryDelaysMs[attempt2];
|
|
18983
20457
|
if (retryDelay !== void 0 && retryDelay > 0 && !await this.waitForRetry(retryDelay)) {
|
|
18984
20458
|
throw this.cancelledError();
|
|
18985
20459
|
}
|
|
@@ -19488,8 +20962,19 @@ async function runLogin(out, env = process.env) {
|
|
|
19488
20962
|
const client = new ApiClient({ apiBaseUrl: serverUrl, syncBaseUrl: serverUrl });
|
|
19489
20963
|
let result;
|
|
19490
20964
|
try {
|
|
19491
|
-
|
|
19492
|
-
|
|
20965
|
+
const outcome = await client.login({ email, password });
|
|
20966
|
+
if (outcome.kind === "mfa-required") {
|
|
20967
|
+
if (outcome.challenge.enrollmentRequired || !outcome.challenge.methods.includes("totp")) {
|
|
20968
|
+
throw new CliError("This account requires MFA enrollment or a security key. Sign in on the web first, then retry.");
|
|
20969
|
+
}
|
|
20970
|
+
const code = (await askVisible("MFA code (6 digits): ")).trim();
|
|
20971
|
+
if (!/^\d{6}$/.test(code)) throw new CliError("The MFA code must be 6 digits.");
|
|
20972
|
+
result = await client.verifyMfaTotpLogin({ loginToken: outcome.challenge.loginToken, code });
|
|
20973
|
+
} else {
|
|
20974
|
+
result = outcome.result;
|
|
20975
|
+
}
|
|
20976
|
+
} catch (error) {
|
|
20977
|
+
if (error instanceof CliError) throw error;
|
|
19493
20978
|
throw new CliError("Login failed. Check the email and password, then try again.");
|
|
19494
20979
|
}
|
|
19495
20980
|
if (!result?.token || !result?.secret) throw new CliError("The server did not return usable credentials.");
|
|
@@ -19784,11 +21269,11 @@ function reduceSessionReadState(state, change, merge, fingerprintLimit = DEFAULT
|
|
|
19784
21269
|
const { item } = change;
|
|
19785
21270
|
if (state.fingerprints.includes(item.fingerprint)) return state;
|
|
19786
21271
|
if (change.kind === "event" && item.cursor !== null && state.cursor !== null && item.cursor < state.cursor) return state;
|
|
19787
|
-
const
|
|
21272
|
+
const fingerprints2 = [...state.fingerprints, item.fingerprint].slice(-Math.max(1, fingerprintLimit));
|
|
19788
21273
|
return {
|
|
19789
21274
|
value: merge(state.value, item.value),
|
|
19790
21275
|
cursor: item.cursor === null ? state.cursor : Math.max(state.cursor ?? item.cursor, item.cursor),
|
|
19791
|
-
fingerprints
|
|
21276
|
+
fingerprints: fingerprints2
|
|
19792
21277
|
};
|
|
19793
21278
|
}
|
|
19794
21279
|
function record(value2) {
|
|
@@ -20114,12 +21599,12 @@ const codexUltraEffortOptions = [
|
|
|
20114
21599
|
function getEffortOptionsForAgent(agent, model) {
|
|
20115
21600
|
if (model === "default") return [];
|
|
20116
21601
|
if (agent === "codex") {
|
|
20117
|
-
if (model === "gpt-5.6-sol" || model === "gpt-5.6-terra") return codexUltraEffortOptions;
|
|
21602
|
+
if (model === "gpt-6-astra" || model === "gpt-5.6-sol" || model === "gpt-5.6-terra") return codexUltraEffortOptions;
|
|
20118
21603
|
if (model === "gpt-5.6-luna") return codexMaxEffortOptions;
|
|
20119
21604
|
return codexEffortOptions;
|
|
20120
21605
|
}
|
|
20121
21606
|
if (agent === "gemini" || agent === "opencode" || agent === "grok") return [];
|
|
20122
|
-
if (model === "claude-fable-5") return claudeFableEffortOptions;
|
|
21607
|
+
if (model === "claude-fable-5-1") return claudeFableEffortOptions;
|
|
20123
21608
|
return claudeEffortOptions;
|
|
20124
21609
|
}
|
|
20125
21610
|
function defaultEffortForAgent(agent, model) {
|
|
@@ -20127,7 +21612,7 @@ function defaultEffortForAgent(agent, model) {
|
|
|
20127
21612
|
if (options.length === 0) return null;
|
|
20128
21613
|
if (agent === "codex") {
|
|
20129
21614
|
if (model === "gpt-5.6-sol") return "low";
|
|
20130
|
-
if (model === "gpt-5.6-terra" || model === "gpt-5.6-luna") return "medium";
|
|
21615
|
+
if (model === "gpt-6-astra" || model === "gpt-5.6-terra" || model === "gpt-5.6-luna") return "medium";
|
|
20131
21616
|
}
|
|
20132
21617
|
return options.some((option) => option.key === "high") ? "high" : options[options.length - 1].key;
|
|
20133
21618
|
}
|
|
@@ -20139,13 +21624,14 @@ const newSessionAgents = [
|
|
|
20139
21624
|
];
|
|
20140
21625
|
const claudeModelOptions = [
|
|
20141
21626
|
{ key: "default", label: "Default" },
|
|
20142
|
-
{ key: "claude-fable-5", label: "Fable 5" },
|
|
21627
|
+
{ key: "claude-fable-5-1", label: "Fable 5.1" },
|
|
20143
21628
|
{ key: "claude-opus-5", label: "Opus 5" },
|
|
20144
21629
|
{ key: "claude-sonnet-5", label: "Sonnet 5" },
|
|
20145
21630
|
{ key: "claude-haiku-4-5", label: "Haiku 4.5" }
|
|
20146
21631
|
];
|
|
20147
21632
|
const codexModelOptions = [
|
|
20148
21633
|
{ key: "default", label: "Default" },
|
|
21634
|
+
{ key: "gpt-6-astra", label: "GPT-6 Astra" },
|
|
20149
21635
|
{ key: "gpt-5.6-sol", label: "GPT-5.6 Sol" },
|
|
20150
21636
|
{ key: "gpt-5.6-terra", label: "GPT-5.6 Terra" },
|
|
20151
21637
|
{ key: "gpt-5.6-luna", label: "GPT-5.6 Luna" },
|
|
@@ -20178,10 +21664,18 @@ function getModelsForAgent(agent) {
|
|
|
20178
21664
|
if (agent === "grok") return grokModelOptions;
|
|
20179
21665
|
return claudeModelOptions;
|
|
20180
21666
|
}
|
|
21667
|
+
const RETIRED_MODEL_KEYS = {
|
|
21668
|
+
"claude-fable-5": "claude-fable-5-1"
|
|
21669
|
+
};
|
|
21670
|
+
function isKnownModelForAgent(agent, model) {
|
|
21671
|
+
const current = RETIRED_MODEL_KEYS[model] ?? model;
|
|
21672
|
+
return getModelsForAgent(agent).some((option) => option.key === current);
|
|
21673
|
+
}
|
|
20181
21674
|
function normalizeModelForAgent(agent, model) {
|
|
20182
21675
|
if (agent === "opencode") return model || "default";
|
|
21676
|
+
const current = RETIRED_MODEL_KEYS[model] ?? model;
|
|
20183
21677
|
const models = getModelsForAgent(agent);
|
|
20184
|
-
return models.some((option) => option.key ===
|
|
21678
|
+
return models.some((option) => option.key === current) ? current : models[0]?.key ?? "default";
|
|
20185
21679
|
}
|
|
20186
21680
|
function normalizeModelSelectionOptions(agent, current, patch) {
|
|
20187
21681
|
const model = normalizeModelForAgent(agent, patch.model ?? current.model);
|
|
@@ -20236,7 +21730,7 @@ function updateLiveChatSettings(current, patch) {
|
|
|
20236
21730
|
}
|
|
20237
21731
|
let selection = { model: current.model, effort: current.effort };
|
|
20238
21732
|
if (patch.model !== void 0) {
|
|
20239
|
-
if (!
|
|
21733
|
+
if (!isKnownModelForAgent(current.agent, patch.model)) return current;
|
|
20240
21734
|
selection = normalizeModelSelectionOptions(current.agent, selection, { model: patch.model });
|
|
20241
21735
|
}
|
|
20242
21736
|
if (patch.effort !== void 0) {
|
|
@@ -20741,7 +22235,7 @@ function chatUploadRelPath(file, id) {
|
|
|
20741
22235
|
if (file.type.startsWith("image/")) return `.aplus/uploads/${id}.${extensionFor(file)}`;
|
|
20742
22236
|
return `.aplus/uploads/${id}-${safeFileName(file)}`;
|
|
20743
22237
|
}
|
|
20744
|
-
async function uploadChatFileToMachineCore(file, workspaceRoot, deps) {
|
|
22238
|
+
async function uploadChatFileToMachineCore(file, workspaceRoot, deps, relativePath) {
|
|
20745
22239
|
const root = workspaceRoot.trim().replace(/\/+$/, "");
|
|
20746
22240
|
if (!root) throw new Error(translateCurrent("upload.noWorkspaceRoot"));
|
|
20747
22241
|
const id = (deps.now?.() ?? Date.now()).toString(36) + (deps.random?.() ?? Math.random()).toString(36).slice(2, 8);
|
|
@@ -21134,14 +22628,12 @@ function hasNewConversationSettings(request) {
|
|
|
21134
22628
|
return request.model !== void 0 || request.effort !== void 0 || request.useWorktree === true || request.worktreeName !== void 0 || request.worktreeBaseRef !== void 0 || request.files !== void 0 || request.voice === true || request.voiceFile !== void 0;
|
|
21135
22629
|
}
|
|
21136
22630
|
function validateExplicitSelection(request, agent) {
|
|
21137
|
-
if (request.model !== void 0 && !
|
|
22631
|
+
if (request.model !== void 0 && !isKnownModelForAgent(agent, request.model)) {
|
|
21138
22632
|
throw new CliError(`Unknown ${agent} model "${request.model}".`);
|
|
21139
22633
|
}
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
throw new CliError(`Effort "${request.effort}" is not available for ${agent} model "${model}".`);
|
|
21144
|
-
}
|
|
22634
|
+
const model = request.model === void 0 ? DEFAULT_CLI_NEW_CONVERSATION_OPTIONS.model : normalizeModelForAgent(agent, request.model);
|
|
22635
|
+
if (request.effort !== void 0 && !effortChoices(agent, model).some((option) => option.key === request.effort)) {
|
|
22636
|
+
throw new CliError(`Effort "${request.effort}" is not available for ${agent} model "${model}".`);
|
|
21145
22637
|
}
|
|
21146
22638
|
}
|
|
21147
22639
|
async function resolveCliNewConversationOptions(request, ask) {
|
|
@@ -21190,6 +22682,7 @@ const USAGE_SETTINGS_NAVIGATION_DESTINATIONS = [
|
|
|
21190
22682
|
"settings_shortcuts",
|
|
21191
22683
|
"settings_machines",
|
|
21192
22684
|
"settings_mobile",
|
|
22685
|
+
"settings_network",
|
|
21193
22686
|
"settings_env",
|
|
21194
22687
|
"settings_security",
|
|
21195
22688
|
"settings_memory",
|
|
@@ -22087,7 +23580,8 @@ function normalizeSessionRows(rows, candidateKeys) {
|
|
|
22087
23580
|
directory: readString(metadata, "path") ?? "",
|
|
22088
23581
|
summary: readSummary(metadata),
|
|
22089
23582
|
active: row2.active === true,
|
|
22090
|
-
updatedAt: row2.updatedAt ?? 0
|
|
23583
|
+
updatedAt: row2.updatedAt ?? 0,
|
|
23584
|
+
agentState: decryptJsonWithCandidates(row2.agentState, candidateKeys)
|
|
22091
23585
|
});
|
|
22092
23586
|
}
|
|
22093
23587
|
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
@@ -22116,9 +23610,9 @@ async function resolveMachineId(context, query) {
|
|
|
22116
23610
|
const match = matchMachine(machines, query);
|
|
22117
23611
|
if (match.type === "match") return match.machine.id;
|
|
22118
23612
|
if (match.type === "not-found") throw new CliError(`No machine matches "${query}". Run \`saycode machines\` to list them.`);
|
|
22119
|
-
const
|
|
23613
|
+
const candidates2 = match.candidates.map((machine) => ` ${machine.host} (${machine.id.slice(0, 8)})`).join("\n");
|
|
22120
23614
|
throw new CliError(`"${query}" matches more than one machine:
|
|
22121
|
-
${
|
|
23615
|
+
${candidates2}
|
|
22122
23616
|
|
|
22123
23617
|
Use a longer prefix or the machine id.`);
|
|
22124
23618
|
}
|
|
@@ -23455,6 +24949,7 @@ function readAgentEnv(env) {
|
|
|
23455
24949
|
if (!root) return null;
|
|
23456
24950
|
return {
|
|
23457
24951
|
root,
|
|
24952
|
+
scope: normalizeDirectory(env.SAYCODE_AGENT_SCOPE ?? "") || root,
|
|
23458
24953
|
depth: readNonNegativeInt(env.SAYCODE_AGENT_DEPTH, 0),
|
|
23459
24954
|
maxSpawn: Math.min(
|
|
23460
24955
|
readNonNegativeInt(env.SAYCODE_AGENT_MAX_SPAWN, DEFAULT_AGENT_MAX_SPAWN),
|
|
@@ -23473,8 +24968,9 @@ function childAgentEnv(parent, childAgentId) {
|
|
|
23473
24968
|
return {
|
|
23474
24969
|
[AGENT_ENV_FLAG]: "1",
|
|
23475
24970
|
SAYCODE_AGENT_ROOT: parent.root,
|
|
24971
|
+
SAYCODE_AGENT_SCOPE: parent.scope,
|
|
23476
24972
|
SAYCODE_AGENT_DEPTH: String(parent.depth + 1),
|
|
23477
|
-
SAYCODE_AGENT_MAX_SPAWN: String(parent.maxSpawn),
|
|
24973
|
+
SAYCODE_AGENT_MAX_SPAWN: String(Math.min(parent.maxSpawn, DEFAULT_AGENT_MAX_SPAWN)),
|
|
23478
24974
|
SAYCODE_AGENT_ID: childAgentId
|
|
23479
24975
|
};
|
|
23480
24976
|
}
|
|
@@ -23487,6 +24983,9 @@ function isInScope(session, scope) {
|
|
|
23487
24983
|
if (directory === root) return true;
|
|
23488
24984
|
return directory.startsWith(root === "/" ? "/" : `${root}/`);
|
|
23489
24985
|
}
|
|
24986
|
+
function sessionsInScope(sessions, scope) {
|
|
24987
|
+
return sessions.filter((session) => isInScope(session, scope));
|
|
24988
|
+
}
|
|
23490
24989
|
function machineSettingsPaths(input) {
|
|
23491
24990
|
const override = input.env.SAYCODE_HAPPY_HOME?.trim();
|
|
23492
24991
|
const runtimeHome = input.env.HAPPY_HOME_DIR?.trim();
|
|
@@ -23697,7 +25196,7 @@ async function handoffToSibling(runtime, scope, env, handoff, agent, modelOption
|
|
|
23697
25196
|
runtime.recordSpawn({
|
|
23698
25197
|
ts: runtime.now(),
|
|
23699
25198
|
agentKey: key,
|
|
23700
|
-
root: env.
|
|
25199
|
+
root: env.scope,
|
|
23701
25200
|
depth: env.depth,
|
|
23702
25201
|
childSessionId: sessionId,
|
|
23703
25202
|
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
@@ -23801,6 +25300,7 @@ function canControlSpawnedDescendant(records, parentSessionId, childSessionId) {
|
|
|
23801
25300
|
return resolveSubagentControlPermission(lineage, parentSessionId, childSessionId);
|
|
23802
25301
|
}
|
|
23803
25302
|
function ownershipFailure(runtime, env, target) {
|
|
25303
|
+
if (env.depth === 0) return null;
|
|
23804
25304
|
if (!env.sessionId) return agentFail("session_unknown", "This session did not receive APLUS_SESSION_ID.");
|
|
23805
25305
|
if (!runtime.readLineage) return agentFail("not_owned", "spawn lineage is unavailable");
|
|
23806
25306
|
try {
|
|
@@ -23841,6 +25341,143 @@ async function stopOwnedTarget(runtime, env, target) {
|
|
|
23841
25341
|
return agentFail("stop_rejected", error instanceof Error ? error.message : String(error));
|
|
23842
25342
|
}
|
|
23843
25343
|
}
|
|
25344
|
+
const LAST_AGENT_TEXT_PREVIEW_LENGTH = 240;
|
|
25345
|
+
function deriveAgentSessionState(snapshot, active) {
|
|
25346
|
+
if (!active) return "ended";
|
|
25347
|
+
if (awaitingUserInput(snapshot)) return "waiting-input";
|
|
25348
|
+
if (snapshot.thinking) return "responding";
|
|
25349
|
+
return "idle";
|
|
25350
|
+
}
|
|
25351
|
+
function turnOpenInWindow(contents) {
|
|
25352
|
+
let lastEnd = -1;
|
|
25353
|
+
let lastActivity = -1;
|
|
25354
|
+
contents.forEach((content, index) => {
|
|
25355
|
+
if (turnBoundary(content) === "end") lastEnd = index;
|
|
25356
|
+
else if (isTurnActivity(content)) lastActivity = index;
|
|
25357
|
+
});
|
|
25358
|
+
return lastActivity > lastEnd;
|
|
25359
|
+
}
|
|
25360
|
+
const TURN_ACTIVITY_EVENTS = /* @__PURE__ */ new Set(["turn-start", "text", "tool-call-start", "tool-call-progress", "tool-call-end"]);
|
|
25361
|
+
function isTurnActivity(content) {
|
|
25362
|
+
if (content.role === "user") return true;
|
|
25363
|
+
if (content.role !== "session") return false;
|
|
25364
|
+
return TURN_ACTIVITY_EVENTS.has(content.content.ev.t);
|
|
25365
|
+
}
|
|
25366
|
+
function lastAgentTextPreview(message, max = LAST_AGENT_TEXT_PREVIEW_LENGTH) {
|
|
25367
|
+
if (!message) return null;
|
|
25368
|
+
const text2 = message.content;
|
|
25369
|
+
return text2.length <= max ? text2 : `${text2.slice(0, max)}…`;
|
|
25370
|
+
}
|
|
25371
|
+
function pendingOf(snapshot) {
|
|
25372
|
+
return {
|
|
25373
|
+
askUserQuestion: Boolean(snapshot.pendingAskUserQuestion),
|
|
25374
|
+
exitPlanMode: Boolean(snapshot.pendingExitPlanMode),
|
|
25375
|
+
permissionRequests: snapshot.pendingPermissionRequestCount ?? 0
|
|
25376
|
+
};
|
|
25377
|
+
}
|
|
25378
|
+
const STATUS_CONCURRENCY = 4;
|
|
25379
|
+
async function sessionStatus(runtime, session) {
|
|
25380
|
+
let sequenced = [];
|
|
25381
|
+
let error = null;
|
|
25382
|
+
try {
|
|
25383
|
+
sequenced = await runtime.readSequencedMessages(session.id, STATE_WINDOW_MESSAGES);
|
|
25384
|
+
} catch (cause) {
|
|
25385
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
25386
|
+
}
|
|
25387
|
+
let snapshot = createWaitSnapshot();
|
|
25388
|
+
for (const { content } of sequenced) snapshot = foldMessageIntoSnapshot(snapshot, content);
|
|
25389
|
+
snapshot = foldAgentStateIntoSnapshot(snapshot, session.agentState ?? null);
|
|
25390
|
+
if (turnOpenInWindow(sequenced.map((entry) => entry.content))) snapshot = { ...snapshot, thinking: true };
|
|
25391
|
+
return {
|
|
25392
|
+
...sessionSummary(session),
|
|
25393
|
+
state: deriveAgentSessionState(snapshot, session.active),
|
|
25394
|
+
turnEndedAt: snapshot.lastResponseCompletedAt ?? 0,
|
|
25395
|
+
completion: completionSignalOf(snapshot),
|
|
25396
|
+
pending: pendingOf(snapshot),
|
|
25397
|
+
lastAgentText: lastAgentTextPreview(snapshot.finalAgentMessage),
|
|
25398
|
+
lastSeq: sequenced.reduce((max, entry) => Math.max(max, entry.seq), 0),
|
|
25399
|
+
...error !== null ? { error } : {}
|
|
25400
|
+
};
|
|
25401
|
+
}
|
|
25402
|
+
async function listSessionStatus(runtime, scope) {
|
|
25403
|
+
const sessions = sessionsInScope(await runtime.listSessions(), scope);
|
|
25404
|
+
const rows = [];
|
|
25405
|
+
for (let index = 0; index < sessions.length; index += STATUS_CONCURRENCY) {
|
|
25406
|
+
const batch = sessions.slice(index, index + STATUS_CONCURRENCY);
|
|
25407
|
+
rows.push(...await Promise.all(batch.map((session) => sessionStatus(runtime, session))));
|
|
25408
|
+
}
|
|
25409
|
+
return agentOk({ sessions: rows });
|
|
25410
|
+
}
|
|
25411
|
+
async function resolveAll(runtime, scope, sessionIds) {
|
|
25412
|
+
const sessions = await runtime.listSessions();
|
|
25413
|
+
const targets = [];
|
|
25414
|
+
for (const sessionId of sessionIds) {
|
|
25415
|
+
const session = sessions.find((candidate) => candidate.id === sessionId);
|
|
25416
|
+
if (!session) return agentFail("session_not_found", sessionId);
|
|
25417
|
+
if (!isInScope(session, scope)) return agentFail("out_of_scope", sessionId);
|
|
25418
|
+
targets.push({ session, scope });
|
|
25419
|
+
}
|
|
25420
|
+
return targets;
|
|
25421
|
+
}
|
|
25422
|
+
async function waitForAnyTarget(runtime, scope, sessionIds, until, timeoutMs) {
|
|
25423
|
+
const resolved = await resolveAll(runtime, scope, sessionIds);
|
|
25424
|
+
if (!Array.isArray(resolved)) return resolved;
|
|
25425
|
+
const since = runtime.now();
|
|
25426
|
+
const predicate = untilPredicate(until, since);
|
|
25427
|
+
const streams = [];
|
|
25428
|
+
for (const target of resolved) {
|
|
25429
|
+
try {
|
|
25430
|
+
streams.push(await runtime.openStream(target.session.id));
|
|
25431
|
+
} catch (error) {
|
|
25432
|
+
for (const stream2 of streams) stream2.close();
|
|
25433
|
+
throw error;
|
|
25434
|
+
}
|
|
25435
|
+
}
|
|
25436
|
+
const controllers = resolved.map(() => new AbortController());
|
|
25437
|
+
const pins = resolved.map((target, index) => startIdentityPin(runtime, target, controllers[index]));
|
|
25438
|
+
const outcomes = new Map(resolved.map((target) => [target.session.id, "pending"]));
|
|
25439
|
+
try {
|
|
25440
|
+
await new Promise((resolve) => {
|
|
25441
|
+
let settled = 0;
|
|
25442
|
+
resolved.forEach((target, index) => {
|
|
25443
|
+
void waitForSessionState({
|
|
25444
|
+
current: streams[index].current,
|
|
25445
|
+
subscribe: streams[index].subscribe,
|
|
25446
|
+
predicate,
|
|
25447
|
+
timeoutMs,
|
|
25448
|
+
signal: controllers[index].signal
|
|
25449
|
+
}).then((result) => {
|
|
25450
|
+
const id = target.session.id;
|
|
25451
|
+
if (result.outcome === "satisfied") outcomes.set(id, "satisfied");
|
|
25452
|
+
else if (result.outcome === "aborted") {
|
|
25453
|
+
if (pins[index].lost()) outcomes.set(id, "lost");
|
|
25454
|
+
} else outcomes.set(id, "timeout");
|
|
25455
|
+
settled += 1;
|
|
25456
|
+
if (result.outcome === "satisfied" || settled === resolved.length) resolve();
|
|
25457
|
+
});
|
|
25458
|
+
});
|
|
25459
|
+
});
|
|
25460
|
+
} finally {
|
|
25461
|
+
for (const controller of controllers) controller.abort();
|
|
25462
|
+
for (const pin of pins) pin.stop();
|
|
25463
|
+
for (const stream2 of streams) stream2.close();
|
|
25464
|
+
}
|
|
25465
|
+
const satisfied = resolved.filter((target, index) => outcomes.get(target.session.id) !== "lost" && predicate(streams[index].current())).map((target) => target.session.id);
|
|
25466
|
+
const lost = resolved.map((target) => target.session.id).filter((id) => outcomes.get(id) === "lost");
|
|
25467
|
+
const pending = resolved.map((target) => target.session.id).filter((id) => !satisfied.includes(id) && !lost.includes(id));
|
|
25468
|
+
if (satisfied.length > 0) {
|
|
25469
|
+
return agentOk({
|
|
25470
|
+
outcome: "satisfied",
|
|
25471
|
+
until,
|
|
25472
|
+
satisfied,
|
|
25473
|
+
pending,
|
|
25474
|
+
lost,
|
|
25475
|
+
sessions: resolved.filter((target) => satisfied.includes(target.session.id)).map((target) => sessionSummary(target.session))
|
|
25476
|
+
});
|
|
25477
|
+
}
|
|
25478
|
+
if (lost.length === resolved.length) return agentFail("session_not_running", lost.join(","));
|
|
25479
|
+
return agentFail("wait_timeout", `${until} not reached within ${timeoutMs}ms${lost.length ? ` (lost: ${lost.join(",")})` : ""}`);
|
|
25480
|
+
}
|
|
23844
25481
|
const CLI_CONFIGURABLE_AGENTS = [.../* @__PURE__ */ new Set([
|
|
23845
25482
|
...newSessionAgents.map(({ id }) => id),
|
|
23846
25483
|
"gemini"
|
|
@@ -23855,7 +25492,6 @@ function cliSpawnModelOptions() {
|
|
|
23855
25492
|
}));
|
|
23856
25493
|
}
|
|
23857
25494
|
const DEFAULT_READ_LIMIT = 20;
|
|
23858
|
-
const STATE_WINDOW_MESSAGES = 50;
|
|
23859
25495
|
function scopeOf(runtime) {
|
|
23860
25496
|
const env = readAgentEnv(runtime.env);
|
|
23861
25497
|
if (!env) return agentFail("not_agent_env", "This command only runs inside a Saycode-spawned session.");
|
|
@@ -23866,7 +25502,7 @@ function scopeOf(runtime) {
|
|
|
23866
25502
|
if (!machineId) {
|
|
23867
25503
|
return agentFail("out_of_scope", "Could not determine this machine from any happy home settings.");
|
|
23868
25504
|
}
|
|
23869
|
-
return { scope: { root: env.
|
|
25505
|
+
return { scope: { root: env.scope, machineId }, env };
|
|
23870
25506
|
}
|
|
23871
25507
|
function isFailure(value2) {
|
|
23872
25508
|
return typeof value2 === "object" && value2 !== null && "stdout" in value2;
|
|
@@ -23878,13 +25514,13 @@ async function resolveTarget(runtime, scope, sessionId) {
|
|
|
23878
25514
|
if (!isInScope(session, scope)) return agentFail("out_of_scope", sessionId);
|
|
23879
25515
|
return { session, scope };
|
|
23880
25516
|
}
|
|
23881
|
-
async function readSession(runtime, target, last) {
|
|
23882
|
-
const
|
|
25517
|
+
async function readSession(runtime, target, last, since = 0) {
|
|
25518
|
+
const sequenced = await runtime.readSequencedMessages(target.session.id, Math.max(last, STATE_WINDOW_MESSAGES));
|
|
23883
25519
|
let snapshot = createWaitSnapshot();
|
|
23884
25520
|
const text2 = [];
|
|
23885
|
-
for (const message of
|
|
25521
|
+
for (const { seq, content: message } of sequenced) {
|
|
23886
25522
|
snapshot = foldMessageIntoSnapshot(snapshot, message);
|
|
23887
|
-
if (message.role !== "session") continue;
|
|
25523
|
+
if (seq <= since || message.role !== "session") continue;
|
|
23888
25524
|
const envelope = message.content;
|
|
23889
25525
|
if (envelope.ev.t !== "text") continue;
|
|
23890
25526
|
text2.push({
|
|
@@ -23900,6 +25536,8 @@ async function readSession(runtime, target, last) {
|
|
|
23900
25536
|
session: sessionSummary(target.session),
|
|
23901
25537
|
turnEndedAt: snapshot.lastResponseCompletedAt ?? 0,
|
|
23902
25538
|
completion: completionSignalOf(snapshot),
|
|
25539
|
+
// 0 when the window is empty: "start from the beginning", never a stale cursor.
|
|
25540
|
+
lastSeq: sequenced.reduce((max, entry) => Math.max(max, entry.seq), 0),
|
|
23903
25541
|
// `slice(-0)` is `slice(0)` — the whole array. `--last 0` is a reasonable
|
|
23904
25542
|
// way to ask for state without the transcript, and it must not answer with
|
|
23905
25543
|
// every message in the window.
|
|
@@ -23913,11 +25551,11 @@ async function spawnSibling(runtime, env, text2, agent, contextFiles = [], model
|
|
|
23913
25551
|
}
|
|
23914
25552
|
const context = resolveSpawnContextFiles({
|
|
23915
25553
|
paths: contextFiles,
|
|
23916
|
-
root: env.
|
|
25554
|
+
root: env.scope,
|
|
23917
25555
|
isReadableFile: runtime.isReadableFile
|
|
23918
25556
|
});
|
|
23919
25557
|
if (!context.ok) {
|
|
23920
|
-
return context.reason === "out_of_scope" ? agentFail("out_of_scope", `${context.path} is outside ${env.
|
|
25558
|
+
return context.reason === "out_of_scope" ? agentFail("out_of_scope", `${context.path} is outside ${env.scope}`) : agentFail("file_unreadable", `${context.path} could not be read`);
|
|
23921
25559
|
}
|
|
23922
25560
|
const key = spawnBudgetKey(env);
|
|
23923
25561
|
let spawned;
|
|
@@ -23946,7 +25584,7 @@ async function spawnSibling(runtime, env, text2, agent, contextFiles = [], model
|
|
|
23946
25584
|
runtime.recordSpawn({
|
|
23947
25585
|
ts: runtime.now(),
|
|
23948
25586
|
agentKey: key,
|
|
23949
|
-
root: env.
|
|
25587
|
+
root: env.scope,
|
|
23950
25588
|
depth: env.depth,
|
|
23951
25589
|
childSessionId: sessionId,
|
|
23952
25590
|
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
@@ -23976,7 +25614,8 @@ async function runAgentCommand(runtime, args) {
|
|
|
23976
25614
|
spawned = null;
|
|
23977
25615
|
}
|
|
23978
25616
|
return agentOk({
|
|
23979
|
-
root:
|
|
25617
|
+
root: env.root,
|
|
25618
|
+
scope: env.scope,
|
|
23980
25619
|
machineId: scope.machineId,
|
|
23981
25620
|
// Which session this IS. Null when happy-cli did not supply it, and a
|
|
23982
25621
|
// handoff must then be told its source explicitly rather than guess.
|
|
@@ -23990,8 +25629,8 @@ async function runAgentCommand(runtime, args) {
|
|
|
23990
25629
|
});
|
|
23991
25630
|
}
|
|
23992
25631
|
if (args.verb === "ls") {
|
|
23993
|
-
|
|
23994
|
-
return agentOk({ sessions:
|
|
25632
|
+
if (args.status) return listSessionStatus(runtime, scope);
|
|
25633
|
+
return agentOk({ sessions: sessionsInScope(await runtime.listSessions(), scope).map(sessionSummary) });
|
|
23995
25634
|
}
|
|
23996
25635
|
if (args.verb === "spawn") {
|
|
23997
25636
|
const agent = args.agent ?? DEFAULT_SPAWN_AGENT;
|
|
@@ -24002,12 +25641,15 @@ async function runAgentCommand(runtime, args) {
|
|
|
24002
25641
|
}
|
|
24003
25642
|
return spawnSibling(runtime, env, args.text ?? "", agent, args.files ?? [], modelOptions);
|
|
24004
25643
|
}
|
|
25644
|
+
if (args.verb === "wait" && args.sessionIds) {
|
|
25645
|
+
return waitForAnyTarget(runtime, scope, args.sessionIds, args.until ?? "turn-end", clampWaitTimeout(args.timeoutMs));
|
|
25646
|
+
}
|
|
24005
25647
|
if (!args.sessionId) return agentFail("session_not_found", "no session id given");
|
|
24006
25648
|
const target = await resolveTarget(runtime, scope, args.sessionId);
|
|
24007
25649
|
if (isFailure(target)) return target;
|
|
24008
25650
|
switch (args.verb) {
|
|
24009
25651
|
case "read":
|
|
24010
|
-
return readSession(runtime, target, args.last ?? DEFAULT_READ_LIMIT);
|
|
25652
|
+
return readSession(runtime, target, args.last ?? DEFAULT_READ_LIMIT, args.since);
|
|
24011
25653
|
case "wait":
|
|
24012
25654
|
return waitForTarget(runtime, target, args.until ?? "turn-end", clampWaitTimeout(args.timeoutMs));
|
|
24013
25655
|
case "prompt":
|
|
@@ -24043,13 +25685,16 @@ function parseSpawnRecord(value2) {
|
|
|
24043
25685
|
}
|
|
24044
25686
|
const LEDGER_FILE_MODE = 384;
|
|
24045
25687
|
const PREVIEW_MAX_LENGTH = 200;
|
|
24046
|
-
function agentLineagePath(
|
|
25688
|
+
function agentLineagePath(rootDir) {
|
|
25689
|
+
return join(rootDir, ".aplus", "agent-lineage.jsonl");
|
|
25690
|
+
}
|
|
25691
|
+
function legacyAgentLineagePath(homeDir) {
|
|
24047
25692
|
return join(homeDir, ".saycode", "agent-lineage.jsonl");
|
|
24048
25693
|
}
|
|
24049
|
-
function
|
|
25694
|
+
function readLedger(path) {
|
|
24050
25695
|
let raw;
|
|
24051
25696
|
try {
|
|
24052
|
-
raw = readFileSync(
|
|
25697
|
+
raw = readFileSync(path, "utf8");
|
|
24053
25698
|
} catch (error) {
|
|
24054
25699
|
if (error.code === "ENOENT") return [];
|
|
24055
25700
|
throw error;
|
|
@@ -24067,12 +25712,38 @@ function readSpawnRecords(homeDir) {
|
|
|
24067
25712
|
}
|
|
24068
25713
|
return records;
|
|
24069
25714
|
}
|
|
24070
|
-
function
|
|
24071
|
-
return
|
|
25715
|
+
function recordIdentity(record2) {
|
|
25716
|
+
return JSON.stringify([
|
|
25717
|
+
record2.ts,
|
|
25718
|
+
record2.agentKey,
|
|
25719
|
+
record2.root,
|
|
25720
|
+
record2.depth,
|
|
25721
|
+
record2.childSessionId,
|
|
25722
|
+
record2.parentSessionId ?? null,
|
|
25723
|
+
record2.agent,
|
|
25724
|
+
record2.promptPreview,
|
|
25725
|
+
record2.outcome
|
|
25726
|
+
]);
|
|
25727
|
+
}
|
|
25728
|
+
function readSpawnRecords(homeDir, rootDir) {
|
|
25729
|
+
const records = [
|
|
25730
|
+
...readLedger(legacyAgentLineagePath(homeDir)),
|
|
25731
|
+
...readLedger(agentLineagePath(rootDir))
|
|
25732
|
+
];
|
|
25733
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25734
|
+
return records.filter((record2) => {
|
|
25735
|
+
const identity = recordIdentity(record2);
|
|
25736
|
+
if (seen.has(identity)) return false;
|
|
25737
|
+
seen.add(identity);
|
|
25738
|
+
return true;
|
|
25739
|
+
});
|
|
25740
|
+
}
|
|
25741
|
+
function countSpawnsFor(homeDir, rootDir, agentKey) {
|
|
25742
|
+
return readSpawnRecords(homeDir, rootDir).filter((record2) => record2.agentKey === agentKey).length;
|
|
24072
25743
|
}
|
|
24073
|
-
function appendSpawnRecord(
|
|
24074
|
-
const path = agentLineagePath(
|
|
24075
|
-
mkdirSync(join(
|
|
25744
|
+
function appendSpawnRecord(rootDir, record2) {
|
|
25745
|
+
const path = agentLineagePath(rootDir);
|
|
25746
|
+
mkdirSync(join(rootDir, ".aplus"), { recursive: true, mode: 448 });
|
|
24076
25747
|
const entry = { ...record2, promptPreview: record2.promptPreview.slice(0, PREVIEW_MAX_LENGTH) };
|
|
24077
25748
|
appendFileSync(path, `${JSON.stringify(entry)}
|
|
24078
25749
|
`, { mode: LEDGER_FILE_MODE });
|
|
@@ -24219,7 +25890,7 @@ async function recentContents(context, transport, sessionId, limit) {
|
|
|
24219
25890
|
const ciphertext = unwrapEncryptedField(message.content);
|
|
24220
25891
|
if (!ciphertext) continue;
|
|
24221
25892
|
const content = decodeContent(transport, ciphertext);
|
|
24222
|
-
if (content) contents.push(content);
|
|
25893
|
+
if (content) contents.push({ seq: message.seq, content });
|
|
24223
25894
|
}
|
|
24224
25895
|
return contents;
|
|
24225
25896
|
}
|
|
@@ -24252,7 +25923,7 @@ async function openStream(context, transport, sessionId) {
|
|
|
24252
25923
|
else apply(update);
|
|
24253
25924
|
});
|
|
24254
25925
|
try {
|
|
24255
|
-
for (const content of await recentContents(context, transport, sessionId, STREAM_HISTORY_LIMIT)) {
|
|
25926
|
+
for (const { content } of await recentContents(context, transport, sessionId, STREAM_HISTORY_LIMIT)) {
|
|
24256
25927
|
snapshot = foldMessageIntoSnapshot(snapshot, content);
|
|
24257
25928
|
}
|
|
24258
25929
|
} catch (error) {
|
|
@@ -24276,7 +25947,7 @@ async function openStream(context, transport, sessionId) {
|
|
|
24276
25947
|
}
|
|
24277
25948
|
function createAgentRuntime(context, transport, env = process.env) {
|
|
24278
25949
|
const keys = [decodeBase64(context.syncCredential.secret)];
|
|
24279
|
-
const homeDir = context.homeDir;
|
|
25950
|
+
const homeDir = context.homeDir, lineageRoot = readAgentEnv(env)?.scope ?? context.homeDir;
|
|
24280
25951
|
const credentialHome = context.credential.source === "saycode" ? null : dirname(context.credential.path);
|
|
24281
25952
|
return {
|
|
24282
25953
|
env,
|
|
@@ -24286,7 +25957,8 @@ function createAgentRuntime(context, transport, env = process.env) {
|
|
|
24286
25957
|
readFile: readFileOrNull,
|
|
24287
25958
|
isReadableFile: isReadableFileSync,
|
|
24288
25959
|
listSessions: async () => normalizeSessionRows(await context.rest.listSessions(), keys),
|
|
24289
|
-
readMessages: (sessionId, limit) => recentContents(context, transport, sessionId, limit),
|
|
25960
|
+
readMessages: async (sessionId, limit) => (await recentContents(context, transport, sessionId, limit)).map((entry) => entry.content),
|
|
25961
|
+
readSequencedMessages: (sessionId, limit) => recentContents(context, transport, sessionId, limit),
|
|
24290
25962
|
openStream: (sessionId) => openStream(context, transport, sessionId),
|
|
24291
25963
|
sendPrompt: async (sessionId, text2) => createCliSessionConnection({
|
|
24292
25964
|
rest: context.rest,
|
|
@@ -24317,9 +25989,9 @@ function createAgentRuntime(context, transport, env = process.env) {
|
|
|
24317
25989
|
return { sessionId: result.sessionId };
|
|
24318
25990
|
},
|
|
24319
25991
|
newAgentId: () => randomBytes(8).toString("hex"),
|
|
24320
|
-
countSpawns: (agentKey) => countSpawnsFor(homeDir, agentKey),
|
|
24321
|
-
recordSpawn: (record2) => appendSpawnRecord(
|
|
24322
|
-
readLineage: () => readSpawnRecords(homeDir),
|
|
25992
|
+
countSpawns: (agentKey) => countSpawnsFor(homeDir, lineageRoot, agentKey),
|
|
25993
|
+
recordSpawn: (record2) => appendSpawnRecord(lineageRoot, record2),
|
|
25994
|
+
readLineage: () => readSpawnRecords(homeDir, lineageRoot),
|
|
24323
25995
|
steerSession: (sessionId, text2) => steerAgentSession(transport, sessionId, text2),
|
|
24324
25996
|
stopSession: (machineId, sessionId) => stopAgentSession(transport, machineId, sessionId),
|
|
24325
25997
|
// Omitted entirely without API access: a Happy `access.key` is sync-only, so
|