@buzzni/saycode-cli 0.3.3 → 0.5.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 +14 -2
- package/index.mjs +1845 -345
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
-
import { homedir, tmpdir, platform } from "node:os";
|
|
3
|
+
import { homedir, tmpdir, platform, totalmem, cpus } from "node:os";
|
|
4
4
|
import require$$0$4, { Buffer as Buffer$1 } from "buffer";
|
|
5
5
|
import require$$0 from "crypto";
|
|
6
6
|
import require$$0$1 from "fs";
|
|
@@ -16,7 +16,7 @@ import require$$3$1 from "net";
|
|
|
16
16
|
import require$$4$1 from "tls";
|
|
17
17
|
import require$$0$5 from "stream";
|
|
18
18
|
import require$$0$3 from "zlib";
|
|
19
|
-
import { readFileSync, mkdirSync, writeFileSync, rmSync, chmodSync, constants as constants$1, appendFileSync, accessSync, statSync } from "node:fs";
|
|
19
|
+
import { readFileSync, mkdirSync, writeFileSync, rmSync, chmodSync, constants as constants$1, appendFileSync, openSync, fstatSync, readSync, closeSync, accessSync, statSync } from "node:fs";
|
|
20
20
|
import { dirname, join, basename, extname, delimiter } from "node:path";
|
|
21
21
|
import { readFile, stat, mkdtemp, access, rm, mkdir, appendFile, writeFile, chmod } from "node:fs/promises";
|
|
22
22
|
import { execFile, spawn } from "node:child_process";
|
|
@@ -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.5.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) {
|
|
@@ -75,12 +75,23 @@ 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
80
|
if (!target) return fail$1("agent read needs a session id (see `saycode agent ls`)");
|
|
81
|
-
return {
|
|
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": {
|
|
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
|
+
}
|
|
84
95
|
if (!target) return fail$1("agent wait needs a session id (see `saycode agent ls`)");
|
|
85
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 };
|
|
@@ -260,6 +271,14 @@ const machineUsageKo = {
|
|
|
260
271
|
"machineUsage.pollingNote": "{minutes}분 전 · 머신당 5분마다 한 번만 조회",
|
|
261
272
|
"machineUsage.autoSwitched": "{provider} 계정을 자동 전환했습니다: {from} → {to}"
|
|
262
273
|
};
|
|
274
|
+
const hubSessionKo = {
|
|
275
|
+
"hubSession.enable": "이 대화를 프로젝트 허브로",
|
|
276
|
+
"hubSession.disable": "프로젝트 허브 해제",
|
|
277
|
+
"hubSession.hint": "같은 프로젝트의 다른 대화가 턴을 끝내거나 입력을 기다리면 이 대화에 메시지로 알려줍니다",
|
|
278
|
+
"hubSession.newSessionLabel": "이 대화를 프로젝트 허브로 시작",
|
|
279
|
+
"hubSession.newSessionHint": "다른 대화의 완료·질문을 여기로 모읍니다",
|
|
280
|
+
"hubSession.newSessionReplaces": '지금 허브는 "{title}" — 켜면 이 대화로 바뀝니다'
|
|
281
|
+
};
|
|
263
282
|
const taskNotificationKo = {
|
|
264
283
|
"taskNotification.ariaLabel": "백그라운드 작업 알림",
|
|
265
284
|
"taskNotification.status.completed": "작업 완료",
|
|
@@ -316,7 +335,7 @@ const agentBoardKo = {
|
|
|
316
335
|
"agentBoard.columnReviewHint": "카드를 놓으면 리뷰를 요청합니다",
|
|
317
336
|
"agentBoard.columnDone": "완료",
|
|
318
337
|
"agentBoard.columnDoneHint": "카드를 놓으면 대화를 완료로 이동합니다",
|
|
319
|
-
"agentBoard.columnMerged": "PR
|
|
338
|
+
"agentBoard.columnMerged": "PR 병합됨",
|
|
320
339
|
"agentBoard.columnMergedHint": "PR이 머지된 대화",
|
|
321
340
|
"agentBoard.title": "에이전트 보드",
|
|
322
341
|
"agentBoard.description": "실행 중인 모든 에이전트 대화를 한눈에 보고, 카드를 옮겨 다음 작업을 지시하세요.",
|
|
@@ -370,7 +389,7 @@ const agentBoardKo = {
|
|
|
370
389
|
"agentBoard.mergeDialogTitle": "Pull request를 머지할까요?",
|
|
371
390
|
"agentBoard.mergeCopy": "PR #{prNumber}을 squash 방식으로 머지합니다. 이 작업은 GitHub 저장소에 즉시 반영됩니다.",
|
|
372
391
|
"agentBoard.mergeCancel": "취소",
|
|
373
|
-
"agentBoard.mergeConfirm": "
|
|
392
|
+
"agentBoard.mergeConfirm": "병합",
|
|
374
393
|
"agentBoard.cardReopenTitle": "완료를 해제하고 이어서 작업합니다. 완료 시 삭제된 작업 트리는 브랜치 기준으로 복구합니다.",
|
|
375
394
|
"agentBoard.cardReopen": "완료 해제",
|
|
376
395
|
"agentBoard.cardCreatePrTitle": "에이전트에게 commit 및 PR 생성을 요청",
|
|
@@ -399,7 +418,7 @@ const agentBoardKo = {
|
|
|
399
418
|
"agentBoard.findSessionError": "대화를 찾을 수 없습니다. 새로고침 후 다시 시도해주세요.",
|
|
400
419
|
"agentBoard.autopilot.enable": "오토파일럿",
|
|
401
420
|
"agentBoard.autopilot.unavailableWorktree": "오토파일럿 사용 불가 · worktree 필요",
|
|
402
|
-
"agentBoard.autopilot.enableTitle": "작업이 완료되면
|
|
421
|
+
"agentBoard.autopilot.enableTitle": "작업이 완료되면 현재 에이전트에 셀프 리뷰를 시키고, medium 이상 지적이 없으면 Commit & PR까지 자동 진행합니다.",
|
|
403
422
|
"agentBoard.autopilot.waiting": "오토파일럿 · 턴 종료 대기",
|
|
404
423
|
"agentBoard.autopilot.reviewRequested": "오토파일럿 · 리뷰 시작 중",
|
|
405
424
|
"agentBoard.autopilot.reviewing": "오토파일럿 · 독립 리뷰 중",
|
|
@@ -419,6 +438,8 @@ const agentBoardKo = {
|
|
|
419
438
|
"agentBoard.autopilot.blocked.commitPrFailed": "오토파일럿 차단 · PR 요청 전송 실패",
|
|
420
439
|
"agentBoard.autopilot.blocked.noCompletionSignal": "오토파일럿 차단 · 완료 신호 없음",
|
|
421
440
|
"agentBoard.autopilot.blocked.loopDetected": "오토파일럿 차단 · 반복 감지로 중지됨",
|
|
441
|
+
"agentBoard.autopilot.blocked.qualityGateFailed": "품질 게이트 실패 · 같은 세션에서 수정 중",
|
|
442
|
+
"agentBoard.autopilot.blocked.qualityGateBlocked": "오토파일럿 차단 · 품질 게이트 실패",
|
|
422
443
|
"agentBoard.autopilot.loopStopFailed": "반복을 감지했지만 세션 중지에 실패했습니다: {detail}",
|
|
423
444
|
"agentBoard.autopilot.forceProceed": "그래도 진행",
|
|
424
445
|
"agentBoard.autopilot.forceProceedTitle": "리뷰어가 완료를 명시적으로 선언하지 않았습니다. 그래도 Commit & PR을 진행합니다.",
|
|
@@ -432,7 +453,12 @@ const agentBoardKo = {
|
|
|
432
453
|
"agentBoard.verify.phase.bootstrap": "준비",
|
|
433
454
|
"agentBoard.verify.phase.build": "빌드",
|
|
434
455
|
"agentBoard.verify.phase.test": "테스트",
|
|
435
|
-
"agentBoard.verify.phase.start": "구동 확인"
|
|
456
|
+
"agentBoard.verify.phase.start": "구동 확인",
|
|
457
|
+
"agentBoard.verify.detail.noRecipe": "실행할 레시피가 없습니다. package.json 스크립트, Makefile, 또는 .aplus/verify.json이 필요합니다.",
|
|
458
|
+
"agentBoard.verify.detail.phaseFailed": "{phase} 실패 · {reason}",
|
|
459
|
+
"agentBoard.verify.detail.phaseTimeout": "{phase} 시간 초과",
|
|
460
|
+
"agentBoard.verify.detail.notReady": "구동은 됐지만 포트가 응답하지 않습니다",
|
|
461
|
+
"agentBoard.verify.phase.deploy": "배포 확인"
|
|
436
462
|
};
|
|
437
463
|
const fanoutLifecycleKo = {
|
|
438
464
|
"fanoutSection.restoreGroupAria": "병렬 그룹 복원: {title} · 실행 {run}",
|
|
@@ -572,7 +598,7 @@ const orcaPortKo = {
|
|
|
572
598
|
"ciFix.promptIntro": "아래 PR의 CI가 실패했습니다. 실패 로그를 확인하고 원인을 수정해주세요:",
|
|
573
599
|
"shortcuts.action.quickOpen": "Quick Open 팔레트",
|
|
574
600
|
"shortcuts.action.quickOpenDescription": "프로젝트·세션·대화를 한 검색창에서 찾아 바로 이동합니다.",
|
|
575
|
-
"quickOpen.title": "
|
|
601
|
+
"quickOpen.title": "빠른 열기",
|
|
576
602
|
"quickOpen.inputAria": "Quick Open 검색",
|
|
577
603
|
"quickOpen.placeholder": "프로젝트, 세션, 대화 검색…",
|
|
578
604
|
"quickOpen.resultsAria": "Quick Open 결과",
|
|
@@ -623,6 +649,8 @@ const scheduledAutomationsKo = {
|
|
|
623
649
|
"scheduledAutomations.nameLabel": "이름",
|
|
624
650
|
"scheduledAutomations.promptLabel": "프롬프트",
|
|
625
651
|
"scheduledAutomations.agentLabel": "실행 에이전트",
|
|
652
|
+
"scheduledAutomations.personaLabel": "실행 페르소나",
|
|
653
|
+
"scheduledAutomations.personaNone": "없음",
|
|
626
654
|
"scheduledAutomations.scheduleLabel": "스케줄",
|
|
627
655
|
"scheduledAutomations.scheduleInterval": "주기 (분마다)",
|
|
628
656
|
"scheduledAutomations.scheduleDaily": "매일 지정 시각",
|
|
@@ -670,6 +698,8 @@ const scheduledAutomationsKo = {
|
|
|
670
698
|
"scheduledAutomations.outcomeSkippedOverlap": "이전 실행 진행 중(스킵)",
|
|
671
699
|
"scheduledAutomations.outcomeSilent": "이상 없음([SILENT])",
|
|
672
700
|
"scheduledAutomations.outcomeError": "실패",
|
|
701
|
+
"scheduledAutomations.requestedModel": "요청 모델: {model}",
|
|
702
|
+
"scheduledAutomations.fallbackModelUsed": "폴백 모델 사용됨: {model}",
|
|
673
703
|
"scheduledAutomations.validationRequired": "이름과 프롬프트를 입력하세요.",
|
|
674
704
|
"scheduledAutomations.migrationTitle": "기존 자동화 이전",
|
|
675
705
|
"scheduledAutomations.migrationHint": "기존 앱·daemon 자동화를 Happy Server로 안전하게 이전합니다.",
|
|
@@ -753,6 +783,10 @@ const githubTriggersKo = {
|
|
|
753
783
|
"githubTriggers.actionLabel": "액션",
|
|
754
784
|
"githubTriggers.actionNotify": "알림만 (LLM 비용 없음)",
|
|
755
785
|
"githubTriggers.actionStartSession": "세션 시작",
|
|
786
|
+
"githubTriggers.actionVerifyMerge": "머지 검증 (실패 시에만 세션)",
|
|
787
|
+
"mergeVerify.noticePassed": 'PR #{pr} "{title}" 머지 후 검증을 통과했습니다 ({revision})',
|
|
788
|
+
"mergeVerify.revisionMergeCommit": "머지 커밋 {sha}",
|
|
789
|
+
"mergeVerify.revisionBaseFallback": "base 브랜치 tip — 머지 커밋을 확인하지 못해 폴백",
|
|
756
790
|
"githubTriggers.agentLabel": "실행 에이전트",
|
|
757
791
|
"githubTriggers.modelLabel": "모델 (비우면 자동)",
|
|
758
792
|
"githubTriggers.effortLabel": "추론 강도",
|
|
@@ -781,13 +815,15 @@ const githubTriggersKo = {
|
|
|
781
815
|
"githubTriggers.outcomeSessionStarted": "세션 시작",
|
|
782
816
|
"githubTriggers.outcomeSkippedOverlap": "이전 세션 진행 중(스킵)",
|
|
783
817
|
"githubTriggers.outcomeError": "실패",
|
|
818
|
+
"githubTriggers.outcomeVerifyPassed": "검증 통과",
|
|
819
|
+
"githubTriggers.outcomeVerifyFailed": "검증 실패 → 수정 세션",
|
|
784
820
|
"githubTriggers.pollError": "GitHub 조회 실패: {detail} — 다음 주기에 재시도합니다.",
|
|
785
821
|
"githubTriggers.validationRequired": "이름과 템플릿을 입력하세요."
|
|
786
822
|
};
|
|
787
823
|
const memoryHubKo = {
|
|
788
824
|
"memoryHub.button": "메모리",
|
|
789
825
|
"memoryHub.ariaLabel": "메모리 허브",
|
|
790
|
-
"memoryHub.title": "
|
|
826
|
+
"memoryHub.title": "메모리 허브",
|
|
791
827
|
"memoryHub.refresh": "새로고침",
|
|
792
828
|
"memoryHub.openDashboard": "전체 대시보드 열기",
|
|
793
829
|
"memoryHub.loading": "프로젝트 메모리를 불러오는 중…",
|
|
@@ -830,7 +866,19 @@ const extensionInstallKo = {
|
|
|
830
866
|
"artifactPublication.open": "링크 열기",
|
|
831
867
|
"artifactPublication.revoke": "공개 중지",
|
|
832
868
|
"artifactPublication.revoking": "중지 중…",
|
|
833
|
-
"artifactPublication.failed": "공개 링크 작업에 실패했습니다."
|
|
869
|
+
"artifactPublication.failed": "공개 링크 작업에 실패했습니다.",
|
|
870
|
+
"artifactPublication.error.failed": "공개 링크 작업에 실패했습니다.",
|
|
871
|
+
"artifactPublication.error.cancelled": "공개 링크 작업이 취소되었습니다.",
|
|
872
|
+
"artifactPublication.error.timeout": "서버 응답 시간이 초과되었습니다. 잠시 후 다시 시도해 주세요.",
|
|
873
|
+
"artifactPublication.error.network": "서버에 연결할 수 없습니다. 네트워크 상태를 확인해 주세요.",
|
|
874
|
+
"artifactPublication.error.unauthenticated": "로그인 세션이 만료되었습니다. 다시 로그인한 뒤 시도해 주세요.",
|
|
875
|
+
"artifactPublication.error.notFound": "파일을 찾을 수 없습니다. 파일이 저장되었는지 확인해 주세요.",
|
|
876
|
+
"artifactPublication.error.tooLarge": "파일이 너무 큽니다. 공개할 HTML 파일은 10MB 이하여야 합니다.",
|
|
877
|
+
"artifactPublication.error.rateLimited": "공개 링크 요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.",
|
|
878
|
+
"artifactPublication.error.rejected": "서버가 이 파일의 공개 요청을 거부했습니다.",
|
|
879
|
+
"artifactPublication.error.serverError": "서버 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
|
880
|
+
"artifactPublication.error.extensionUnavailable": "확장이 비활성화되었거나 공개 권한이 없습니다. 설정에서 Artifact Publisher를 확인해 주세요.",
|
|
881
|
+
"artifactPublication.error.invalidResponse": "서버 응답을 확인할 수 없어 중단했습니다."
|
|
834
882
|
};
|
|
835
883
|
const personalChatsLandingKo = {
|
|
836
884
|
"personalChats.heroTitle": "무엇을 도와드릴까요?",
|
|
@@ -864,7 +912,7 @@ const personalChatsKo = {
|
|
|
864
912
|
"personalChats.creating": "채팅을 시작하는 중…",
|
|
865
913
|
"personalChats.noOnlineMachine": "온라인 머신이 없습니다. 채팅을 시작하려면 머신을 시작해주세요.",
|
|
866
914
|
"personalChats.localMachinePreparing": "이 컴퓨터를 Saycode에 연결하는 중이에요. 잠시 후 바로 시작할 수 있어요.",
|
|
867
|
-
"personalChats.localMachineAsk": "이
|
|
915
|
+
"personalChats.localMachineAsk": "이 컴퓨터에 Saycode CLI를 설치하면 바로 채팅을 시작할 수 있어요.",
|
|
868
916
|
"personalChats.localMachineFailed": "이 컴퓨터 자동 연결에 실패했어요. 다른 방법으로 연결할 수 있어요.",
|
|
869
917
|
"personalChats.pickMachine": "이 채팅을 실행할 머신을 선택하세요",
|
|
870
918
|
"personalChats.back": "뒤로",
|
|
@@ -928,16 +976,49 @@ const agentLoopDetectionKo = {
|
|
|
928
976
|
"agentBoard.loopDetectedTitle": "같은 툴 호출 또는 텍스트가 짧은 시간 안에 반복되고 있습니다. 멈춰 있을 수 있습니다.",
|
|
929
977
|
"settings.agentLoopAutoStop.enable": "반복 감지 시 세션 자동 중지",
|
|
930
978
|
"settings.agentLoopAutoStop.help": "오토파일럿이 관리하는 세션이 같은 툴 호출이나 텍스트를 반복하며 멈춘 것으로 보이면 자동으로 응답을 중지합니다. 기본은 꺼짐 — 오탐으로 정상 작업을 멈추는 것이 놓치는 것보다 나쁩니다.",
|
|
931
|
-
"settings.agentLoopAutoStop.saveFailed": "설정을 저장하지 못했습니다."
|
|
979
|
+
"settings.agentLoopAutoStop.saveFailed": "설정을 저장하지 못했습니다.",
|
|
980
|
+
"settings.autonomousQualityGate.enable": "오토파일럿 자율 품질 게이트 사용",
|
|
981
|
+
"settings.autonomousQualityGate.help": "리뷰와 Commit & PR 전에 프로젝트 검증을 실행하고, 실패하면 같은 세션에 제한된 수정 요청을 보냅니다. 기본은 꺼짐입니다.",
|
|
982
|
+
"settings.autonomousQualityGate.saveFailed": "품질 게이트 설정을 저장하지 못했습니다.",
|
|
983
|
+
"qualityGate.statusAria": "자율 품질 게이트 상태",
|
|
984
|
+
"qualityGate.stage": "품질 게이트 · {stage}",
|
|
985
|
+
"qualityGate.attempt": "시도 {attempt}/{max}",
|
|
986
|
+
"qualityGate.usage": "수정 {continuations}/{maxContinuations} · 턴 {turns}/{maxTurns} · 토큰 {tokens}/{maxTokens}",
|
|
987
|
+
"qualityGate.lastPhase": "{phase} · {result}",
|
|
988
|
+
"qualityGate.limit": "한도 도달: {reason}",
|
|
989
|
+
"qualityGate.pause": "품질 게이트 일시정지",
|
|
990
|
+
"qualityGate.resume": "품질 게이트 재개",
|
|
991
|
+
"qualityGate.stop": "품질 게이트 중지"
|
|
932
992
|
};
|
|
933
993
|
const autopilotSelfReviewRoundsKo = {
|
|
934
994
|
"settings.autopilotSelfReviewRounds.label": "오토파일럿 셀프 리뷰 반복 횟수",
|
|
935
|
-
"settings.autopilotSelfReviewRounds.help": '오토파일럿이 "현재 에이전트로 점검"을 몇 번까지 이어서 시킬지 정합니다.
|
|
995
|
+
"settings.autopilotSelfReviewRounds.help": '오토파일럿이 "현재 에이전트로 점검"을 몇 번까지 이어서 시킬지 정합니다. 한 라운드에서 medium 이상 지적이 나오면 남은 횟수만큼 다시 점검하게 하고, low 이하만 남으면 남은 횟수와 상관없이 바로 Commit & PR로 넘어갑니다. 지정한 횟수를 다 쓰고도 medium 이상이 남으면 멈추고 사람에게 넘깁니다. 기본은 7회.',
|
|
936
996
|
"settings.autopilotSelfReviewRounds.once": "1회 (반복 없음)",
|
|
937
997
|
"settings.autopilotSelfReviewRounds.times": "{count}회",
|
|
938
998
|
"settings.autopilotSelfReviewRounds.saveFailed": "설정을 저장하지 못했습니다.",
|
|
939
999
|
"agentBoard.autopilot.reviewingSelfRound": "오토파일럿 · 셀프 리뷰 중 {round}/{total}"
|
|
940
1000
|
};
|
|
1001
|
+
const memoryGuardrailsKo = {
|
|
1002
|
+
"settings.memoryGuardrails.help": "기억으로 남기지 말아야 할 항목을 정합니다. 기본 규칙은 끌 수 있고, 프로젝트 전용 규칙을 추가할 수 있습니다.",
|
|
1003
|
+
"settings.memoryGuardrails.saveFailed": "설정을 저장하지 못했습니다.",
|
|
1004
|
+
"settings.memoryGuardrails.tooLong": "규칙 전체 길이가 {max}자를 넘어 저장할 수 없습니다.",
|
|
1005
|
+
"settings.memoryGuardrails.removeAria": "{text} 삭제",
|
|
1006
|
+
"settings.memoryGuardrails.remove": "삭제",
|
|
1007
|
+
"settings.memoryGuardrails.addLabel": "새 금지 항목",
|
|
1008
|
+
"settings.memoryGuardrails.addPlaceholder": "예: 이 프로젝트의 임시 브랜치 이름은 기억하지 않는다",
|
|
1009
|
+
"settings.memoryGuardrails.add": "추가"
|
|
1010
|
+
};
|
|
1011
|
+
const memoryEventsKo = {
|
|
1012
|
+
"settings.memoryEvents.ariaLabel": "기억 열람",
|
|
1013
|
+
"settings.memoryEvents.unavailable": "기억 대시보드가 실행 중이 아닙니다.",
|
|
1014
|
+
"settings.memoryEvents.searchLabel": "기억 검색",
|
|
1015
|
+
"settings.memoryEvents.prevPage": "이전",
|
|
1016
|
+
"settings.memoryEvents.nextPage": "다음",
|
|
1017
|
+
"settings.memoryEvents.delete": "삭제",
|
|
1018
|
+
"settings.memoryEvents.confirmDelete": "정말 삭제",
|
|
1019
|
+
"settings.memoryEvents.deleteHint": "삭제한 기억은 되돌릴 수 없습니다.",
|
|
1020
|
+
"settings.memoryEvents.deleteFailed": "삭제하지 못했습니다: {reason}"
|
|
1021
|
+
};
|
|
941
1022
|
const externalWorktreeInboxKo = {
|
|
942
1023
|
"externalWorktreeInbox.title": "앱 밖에서 만든 worktree",
|
|
943
1024
|
"externalWorktreeInbox.description": "이 저장소에 앱이 만들지 않은 worktree 가 있습니다. 채택하면 목록과 새 대화 시작에서 고를 수 있어요.",
|
|
@@ -968,6 +1049,99 @@ const externalSessionVaultKo = {
|
|
|
968
1049
|
"externalSessionVault.rejectedNotOwned": "이 프로젝트 폴더에서 돌린 대화가 아니라서 이어가지 않았습니다.",
|
|
969
1050
|
"externalSessionVault.resumeFailed": "그 대화를 이어가지 못했습니다."
|
|
970
1051
|
};
|
|
1052
|
+
const orgSearchKo = {
|
|
1053
|
+
"orgAdmin.tab.search": "검색 (Elasticsearch)",
|
|
1054
|
+
"orgSearch.admin.title": "Elasticsearch 기반 조직 검색",
|
|
1055
|
+
"orgSearch.admin.description": "조직의 대화를 조직 소유 Elasticsearch에 색인해 멤버가 키워드로 검색할 수 있게 합니다. 각 멤버는 자기가 참여한 프로젝트만 볼 수 있으며, 이는 멤버별 필터 alias와 API 키로 Elasticsearch가 직접 강제합니다.",
|
|
1056
|
+
"orgSearch.admin.unsupported": "이 서버 또는 데스크탑 버전은 아직 조직 검색을 지원하지 않습니다.",
|
|
1057
|
+
"orgSearch.admin.readOnly": "조직 owner/admin만 이 연동을 변경할 수 있습니다.",
|
|
1058
|
+
"orgSearch.form.url": "Elasticsearch 주소",
|
|
1059
|
+
"orgSearch.form.urlPlaceholder": "http://elasticsearch.internal:9200",
|
|
1060
|
+
"orgSearch.form.username": "관리자 ID",
|
|
1061
|
+
"orgSearch.form.password": "관리자 비밀번호",
|
|
1062
|
+
"orgSearch.form.passwordHint": "전용 운영자 사용자를 만드는 데 한 번만 쓰입니다. 관리자 비밀번호는 저장하지 않습니다.",
|
|
1063
|
+
"orgSearch.form.profile": "analyzer 프로필",
|
|
1064
|
+
"orgSearch.form.profile.nori": "nori + ngram (한국어 형태소, 권장)",
|
|
1065
|
+
"orgSearch.form.profile.standard": "standard + ngram (nori 플러그인 없음)",
|
|
1066
|
+
"orgSearch.form.test": "연결 테스트",
|
|
1067
|
+
"orgSearch.form.testing": "테스트 중…",
|
|
1068
|
+
"orgSearch.form.save": "연결하고 인덱스 만들기",
|
|
1069
|
+
"orgSearch.form.saving": "연결 중…",
|
|
1070
|
+
"orgSearch.probe.version": "Elasticsearch 버전",
|
|
1071
|
+
"orgSearch.probe.security": "보안 활성",
|
|
1072
|
+
"orgSearch.probe.license": "라이선스",
|
|
1073
|
+
"orgSearch.probe.nori": "analysis-nori 플러그인",
|
|
1074
|
+
"orgSearch.probe.privileges": "보안 관리 권한 (운영자 사용자 생성)",
|
|
1075
|
+
"orgSearch.probe.yes": "예",
|
|
1076
|
+
"orgSearch.probe.no": "아니오",
|
|
1077
|
+
"orgSearch.probe.unknown": "알 수 없음",
|
|
1078
|
+
"orgSearch.verdict.ok": "연결에 성공했습니다. 저장하면 운영자 사용자와 인덱스를 만듭니다.",
|
|
1079
|
+
"orgSearch.verdict.unreachable": "Elasticsearch에 닿을 수 없습니다. URL과 이 머신이 같은 네트워크(VPN)에 있는지 확인하세요.",
|
|
1080
|
+
"orgSearch.verdict.unauthorized": "인증에 실패했습니다. 관리자 ID와 비밀번호를 확인하세요.",
|
|
1081
|
+
"orgSearch.verdict.unsupported-version": "Elasticsearch 8 이상이 필요합니다.",
|
|
1082
|
+
"orgSearch.verdict.security-disabled": "이 클러스터는 보안이 꺼져 있어 프로젝트별 접근 권한을 강제할 수 없습니다. xpack.security를 먼저 켜세요.",
|
|
1083
|
+
"orgSearch.verdict.insufficient-privileges": "이 사용자는 보안을 관리할 수 없습니다. manage_security와 manage_api_key 권한이 있는 사용자를 쓰세요.",
|
|
1084
|
+
"orgSearch.saved.title": "조직 검색이 연결되었습니다.",
|
|
1085
|
+
"orgSearch.saved.detail": "운영자 사용자 {operator} · 인덱스 {index}",
|
|
1086
|
+
"orgSearch.saved.existingIndex": "매핑이 일치하는 기존 인덱스를 그대로 사용했습니다.",
|
|
1087
|
+
"orgSearch.summary.url": "Elasticsearch 주소",
|
|
1088
|
+
"orgSearch.summary.index": "인덱스",
|
|
1089
|
+
"orgSearch.summary.profile": "analyzer 프로필",
|
|
1090
|
+
"orgSearch.summary.operator": "운영자 사용자",
|
|
1091
|
+
"orgSearch.summary.updated": "갱신",
|
|
1092
|
+
"orgSearch.disconnect": "연동 해제",
|
|
1093
|
+
"orgSearch.disconnect.title": "조직 검색 연동을 해제할까요?",
|
|
1094
|
+
"orgSearch.disconnect.body": "Elasticsearch에서 멤버 키·alias·조직 인덱스를 삭제하고, 멤버의 조직 검색 탭은 즉시 사라집니다. 대화 자체에는 영향이 없습니다.",
|
|
1095
|
+
"orgSearch.disconnect.operatorSection": "운영자 사용자도 삭제 (관리자 계정이 다시 필요)",
|
|
1096
|
+
"orgSearch.disconnect.confirm": "해제하고 삭제",
|
|
1097
|
+
"orgSearch.disconnect.working": "해제 중…",
|
|
1098
|
+
"orgSearch.disconnect.cancel": "취소",
|
|
1099
|
+
"orgSearch.disconnected": "해제했습니다. 인덱스 {indices}개, alias {aliases}개, 멤버 키 {keys}개를 삭제했습니다.",
|
|
1100
|
+
"orgSearch.disconnected.operatorKept": "관리자 자격증명이 없어 운영자 사용자는 남겨 두었습니다.",
|
|
1101
|
+
"orgSearch.error.title": "마지막 작업이 실패했습니다",
|
|
1102
|
+
"orgSearch.acl.title": "권한 동기화 (멤버별 alias·키)",
|
|
1103
|
+
"orgSearch.acl.upToDate": "권한이 최신입니다",
|
|
1104
|
+
"orgSearch.acl.waiting": "{count}건 대기",
|
|
1105
|
+
"orgSearch.acl.members": "멤버 {count}명",
|
|
1106
|
+
"orgSearch.acl.tick.applied": "마지막 동기화: 변경 {actions}건 적용, 실패 {failures}건.",
|
|
1107
|
+
"orgSearch.acl.tick.inSync": "마지막 동기화에서 변경할 것이 없었습니다.",
|
|
1108
|
+
"orgSearch.acl.tick.elsewhere": "다른 관리자의 데스크탑이 동기화를 맡고 있습니다.",
|
|
1109
|
+
"orgSearch.acl.tick.failed": "마지막 동기화 실패: {message}",
|
|
1110
|
+
"orgSearch.progress.title": "색인 진행률",
|
|
1111
|
+
"orgSearch.progress.phase.idle": "색인할 대화 없음",
|
|
1112
|
+
"orgSearch.progress.phase.waiting": "색인할 멤버 대기 중",
|
|
1113
|
+
"orgSearch.progress.phase.indexing": "색인 중",
|
|
1114
|
+
"orgSearch.progress.phase.done": "색인 완료",
|
|
1115
|
+
"orgSearch.progress.sessions": "세션 {done}/{total} ({percent}%)",
|
|
1116
|
+
"orgSearch.progress.messages": "메시지 {count}개 색인됨",
|
|
1117
|
+
"orgSearch.progress.failed": "실패 {count}건",
|
|
1118
|
+
"orgSearch.progress.leases": "활성 색인기 {count}개",
|
|
1119
|
+
"orgSearch.progress.updated": "{time} 갱신",
|
|
1120
|
+
"orgSearch.progress.project": "프로젝트",
|
|
1121
|
+
"orgSearch.progress.phaseColumn": "상태",
|
|
1122
|
+
"orgSearch.progress.sessionsColumn": "세션",
|
|
1123
|
+
"orgSearch.progress.leader": "색인기",
|
|
1124
|
+
"search.tabOrg": "조직",
|
|
1125
|
+
"orgSearch.tab.keyPending": "조직 관리자의 데스크탑이 검색 키를 발급하는 중입니다. 잠시 후 다시 시도하세요.",
|
|
1126
|
+
"orgSearch.tab.unavailable": "지금은 조직 검색을 사용할 수 없습니다.",
|
|
1127
|
+
"orgSearch.tab.error.unauthorized": "검색 키가 거부되었습니다. 새 키를 요청했으니 잠시 후 다시 시도하세요.",
|
|
1128
|
+
"orgSearch.tab.error.forbidden": "Elasticsearch가 이 검색을 거부했습니다. 권한 동기화가 늦었을 수 있으며 관리자 데스크탑이 곧 맞춥니다.",
|
|
1129
|
+
"orgSearch.tab.error.unreachable": "조직 Elasticsearch에 닿을 수 없습니다. 같은 네트워크(VPN)에 있는지 확인하세요.",
|
|
1130
|
+
"orgSearch.tab.error.timeout": "Elasticsearch가 제때 응답하지 않았습니다.",
|
|
1131
|
+
"orgSearch.tab.error.not-configured": "이 데스크탑에는 아직 조직 검색이 설정되지 않았습니다.",
|
|
1132
|
+
"orgSearch.tab.error.error": "검색에 실패했습니다.",
|
|
1133
|
+
"orgSearch.eval.title": "검색 품질 평가",
|
|
1134
|
+
"orgSearch.eval.description": "색인된 내 프롬프트 {count}건을 표본으로 뽑아 실제 검색어처럼 변형(부분 문자열·키워드·오타·식별자 등)한 뒤, 각 검색어가 자기 대화를 찾는지를 Elasticsearch와 이 데스크탑의 로컬 인덱스에서 나란히 확인합니다. 프롬프트는 앱 밖으로 나가지 않습니다.",
|
|
1135
|
+
"orgSearch.eval.run": "품질 평가 실행",
|
|
1136
|
+
"orgSearch.eval.running": "평가 중…",
|
|
1137
|
+
"orgSearch.eval.emptyIndex": "로컬 인덱스에 표본으로 쓸 프롬프트가 아직 없습니다.",
|
|
1138
|
+
"orgSearch.eval.sampled": "프롬프트 {prompts}건, 검색어 {cases}건",
|
|
1139
|
+
"orgSearch.eval.kind": "변형 종류",
|
|
1140
|
+
"orgSearch.eval.engine.es": "Elasticsearch(조직)",
|
|
1141
|
+
"orgSearch.eval.engine.local": "로컬 인덱스",
|
|
1142
|
+
"orgSearch.eval.overall": "전체",
|
|
1143
|
+
"orgSearch.eval.legend": "칸: Recall@1 / @5 / @10 · MRR · p95 지연. 같은 문구를 여러 대화에서 썼다면 다른 대화가 1위인 것도 정상이라, 100% 미만이 곧 결함은 아닙니다."
|
|
1144
|
+
};
|
|
971
1145
|
const operationModelRetryKo = {
|
|
972
1146
|
"review.job.fallbackModelUsed": "폴백 모델 사용됨: {model}"
|
|
973
1147
|
};
|
|
@@ -1056,7 +1230,46 @@ const accountSettingsKo = {
|
|
|
1056
1230
|
"settingsGroup.devices": "기기",
|
|
1057
1231
|
"settingsGroup.app": "앱",
|
|
1058
1232
|
"shortcuts.action.settings": "환경설정 열기",
|
|
1059
|
-
"shortcuts.action.settingsDescription": "환경설정 창을 엽니다."
|
|
1233
|
+
"shortcuts.action.settingsDescription": "환경설정 창을 엽니다.",
|
|
1234
|
+
"accountKeyRecovery.title": "암호화된 데이터가 잠겨 있습니다",
|
|
1235
|
+
"accountKeyRecovery.description": "이 데스크톱에서 암호화된 데이터를 잠금 해제하려면 E2EE 복구 키를 입력하세요. 복구 키는 이 기기를 벗어나지 않습니다.",
|
|
1236
|
+
"accountKeyRecovery.label": "E2EE 복구 키",
|
|
1237
|
+
"accountKeyRecovery.unavailable": "이 계정에서는 복구 키 잠금 해제를 사용할 수 없습니다.",
|
|
1238
|
+
"accountKeyRecovery.invalid": "복구 키로 암호화된 데이터를 잠금 해제하지 못했습니다.",
|
|
1239
|
+
"accountKeyRecovery.failed": "복구 키 잠금 해제에 실패했습니다.",
|
|
1240
|
+
"accountKeyRecovery.action": "암호화된 데이터 잠금 해제",
|
|
1241
|
+
"accountKeyRecovery.busy": "잠금 해제 중…",
|
|
1242
|
+
"accountKeyDevice.title": "신뢰 기기",
|
|
1243
|
+
"accountKeyDevice.description": "기존 신뢰 기기에서 이 데스크톱을 승인하거나 대기 중인 기기 요청을 검토하세요.",
|
|
1244
|
+
"accountKeyDevice.loading": "신뢰 기기 상태를 불러오는 중…",
|
|
1245
|
+
"accountKeyDevice.loadFailed": "기기 승인 상태를 불러오지 못했습니다.",
|
|
1246
|
+
"accountKeyDevice.stableIdentityUnavailable": "안정적인 기기 ID를 사용할 수 없어 신뢰 기기 등록을 진행할 수 없습니다.",
|
|
1247
|
+
"accountKeyDevice.secureStorageUnavailable": "보안 기기 저장소를 사용할 수 없어 신뢰 기기 등록을 진행할 수 없습니다.",
|
|
1248
|
+
"accountKeyDevice.qrFailed": "기기 승인 QR을 만들지 못했습니다.",
|
|
1249
|
+
"accountKeyDevice.qrAlt": "기기 승인 QR 코드",
|
|
1250
|
+
"accountKeyDevice.requestFailed": "기기 승인 요청에 실패했습니다.",
|
|
1251
|
+
"accountKeyDevice.pending": "신뢰 기기의 승인을 기다리고 있습니다.",
|
|
1252
|
+
"accountKeyDevice.completeFailed": "기기 승인을 완료하지 못했습니다.",
|
|
1253
|
+
"accountKeyDevice.approved": "이 데스크톱이 신뢰 기기로 승인되었습니다.",
|
|
1254
|
+
"accountKeyDevice.cancelled": "이 기기에서 승인 요청을 취소했습니다.",
|
|
1255
|
+
"accountKeyDevice.cancelFailed": "기기 승인 요청을 취소하지 못했습니다.",
|
|
1256
|
+
"accountKeyDevice.requestsFailed": "대기 중인 기기 요청을 불러오지 못했습니다.",
|
|
1257
|
+
"accountKeyDevice.approveFailed": "기기를 승인하지 못했습니다.",
|
|
1258
|
+
"accountKeyDevice.rejectFailed": "기기 요청을 거절하지 못했습니다.",
|
|
1259
|
+
"accountKeyDevice.revokeFailed": "신뢰 기기를 폐기하지 못했습니다.",
|
|
1260
|
+
"accountKeyDevice.cleanupFailed": "기기는 폐기됐지만 로컬 자격 증명을 삭제하지 못했습니다.",
|
|
1261
|
+
"accountKeyDevice.lockPersistFailed": "기기는 폐기됐지만 로컬 세션 잠금을 저장하지 못했습니다.",
|
|
1262
|
+
"accountKeyDevice.request": "기기 승인 요청",
|
|
1263
|
+
"accountKeyDevice.check": "승인 확인",
|
|
1264
|
+
"accountKeyDevice.cancel": "요청 취소",
|
|
1265
|
+
"accountKeyDevice.scanBefore": "{time} 전에 승인된 기기에서 이 QR을 스캔하세요.",
|
|
1266
|
+
"accountKeyDevice.expires": "{time}에 만료",
|
|
1267
|
+
"accountKeyDevice.approve": "기기 승인",
|
|
1268
|
+
"accountKeyDevice.reject": "기기 거절",
|
|
1269
|
+
"accountKeyDevice.refresh": "요청 새로 고침",
|
|
1270
|
+
"accountKeyDevice.current": "현재 데스크톱",
|
|
1271
|
+
"accountKeyDevice.lastUsed": "마지막 사용: {time}",
|
|
1272
|
+
"accountKeyDevice.revoke": "신뢰 기기 폐기"
|
|
1060
1273
|
};
|
|
1061
1274
|
const projectScopeKo = {
|
|
1062
1275
|
"projectScope.protected": "프로젝트 범위 보호됨",
|
|
@@ -1106,7 +1319,7 @@ const machineSettingsConsoleKo = {
|
|
|
1106
1319
|
"machineConsole.daemonUnknown": "daemon 미확인",
|
|
1107
1320
|
"machineConsole.daemonStatus": "daemon 상태",
|
|
1108
1321
|
"machineConsole.daemonVersion": "daemon 버전",
|
|
1109
|
-
"machineConsole.daemonVersionValue": "
|
|
1322
|
+
"machineConsole.daemonVersionValue": "데몬 {version}",
|
|
1110
1323
|
"machineConsole.rpcStatus": "RPC 상태",
|
|
1111
1324
|
"machineConsole.rpcAvailableStatus": "RPC 사용 가능",
|
|
1112
1325
|
"machineConsole.rpcUnavailableStatus": "RPC 사용 불가",
|
|
@@ -1194,7 +1407,17 @@ const newSessionProfileKo = {
|
|
|
1194
1407
|
"newSession.profile.fallbackAgent": "{value} 에이전트를 지금 쓸 수 없어 현재 선택을 유지했습니다.",
|
|
1195
1408
|
"newSession.profile.fallbackModel": "{value} 모델이 목록에 없어 기본값으로 시작합니다.",
|
|
1196
1409
|
"newSession.profile.fallbackEffort": "{value} 강도를 이 모델에서 쓸 수 없어 기본값으로 시작합니다.",
|
|
1197
|
-
"newSession.profile.fallbackWorktree": "이 프로젝트는 git 저장소가 아니라 작업 사본을 끕니다."
|
|
1410
|
+
"newSession.profile.fallbackWorktree": "이 프로젝트는 git 저장소가 아니라 작업 사본을 끕니다.",
|
|
1411
|
+
// specs/desktop-session-personas T9
|
|
1412
|
+
"newSession.profile.editPersona": "{name} 페르소나 편집",
|
|
1413
|
+
"newSession.profile.instructionsLabel": "{name} 지시문",
|
|
1414
|
+
"newSession.profile.instructionsPlaceholder": "예: 리뷰 관점에서만 답하라",
|
|
1415
|
+
"newSession.profile.instructionsTooLong": "지시문이 너무 깁니다 ({max}자 이하로 줄여주세요).",
|
|
1416
|
+
"newSession.profile.instructionsSave": "{name} 지시문 저장",
|
|
1417
|
+
"newSession.profile.skillsLabel": "배포할 스킬",
|
|
1418
|
+
"newSession.profile.badgeIconLabel": "{name} 표식 아이콘",
|
|
1419
|
+
"newSession.profile.badgeColorLabel": "{name} 표식 색상",
|
|
1420
|
+
"newSession.profile.badgeSave": "{name} 표식 저장"
|
|
1198
1421
|
};
|
|
1199
1422
|
const agentBoardBroadcastKo = {
|
|
1200
1423
|
"agentBoard.broadcastSelected": "선택됨",
|
|
@@ -1252,6 +1475,16 @@ const officeViewerKo = {
|
|
|
1252
1475
|
"officeViewer.unsupported": "미리보기를 지원하지 않는 형식입니다.",
|
|
1253
1476
|
"officeViewer.unsupportedHint": "원본 앱에서 열어 확인하세요."
|
|
1254
1477
|
};
|
|
1478
|
+
const additionalDirectoryMountWarningKo = {
|
|
1479
|
+
"additionalDirectories.mountMissing": "추가 디렉터리 {count}개를 자동 마운트에서 제외하고 세션을 시작했습니다.",
|
|
1480
|
+
"additionalDirectories.mountRuntimeUnsupported": "이 머신의 Happy CLI가 추가 디렉터리 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
|
|
1481
|
+
"additionalDirectories.mountLimit": "추가 디렉터리 마운트 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다.",
|
|
1482
|
+
"additionalDirectories.relatedMountMissing": "관련 프로젝트 디렉터리 {count}개를 자동 마운트에서 제외하고 세션을 시작했습니다.",
|
|
1483
|
+
"additionalDirectories.relatedMountLinkLookupFailed": "관련 프로젝트 목록을 불러오지 못해 자동 마운트 없이 세션을 시작했습니다.",
|
|
1484
|
+
"additionalDirectories.relatedMountLookupFailed": "관련 프로젝트 {count}개를 불러오지 못해 제외하고 세션을 시작했습니다.",
|
|
1485
|
+
"additionalDirectories.relatedMountRuntimeUnsupported": "이 머신의 Happy CLI가 관련 프로젝트 자동 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
|
|
1486
|
+
"additionalDirectories.relatedMountLimit": "관련 프로젝트 디렉터리 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다."
|
|
1487
|
+
};
|
|
1255
1488
|
const additionalDirectoriesKo = {
|
|
1256
1489
|
"additionalDirectories.title": "추가 디렉터리",
|
|
1257
1490
|
"additionalDirectories.add": "디렉터리 추가",
|
|
@@ -1272,11 +1505,7 @@ const additionalDirectoriesKo = {
|
|
|
1272
1505
|
"additionalDirectories.machineMismatch": "현재 머신과 다른 위치에서 선택한 디렉터리입니다.",
|
|
1273
1506
|
"additionalDirectories.saveFailed": "디렉터리를 등록하지 못했습니다.",
|
|
1274
1507
|
"additionalDirectories.folderLoadFailed": "디렉터리를 불러오지 못했습니다.",
|
|
1275
|
-
|
|
1276
|
-
"additionalDirectories.relatedMountLinkLookupFailed": "관련 프로젝트 목록을 불러오지 못해 자동 마운트 없이 세션을 시작했습니다.",
|
|
1277
|
-
"additionalDirectories.relatedMountLookupFailed": "관련 프로젝트 {count}개를 불러오지 못해 제외하고 세션을 시작했습니다.",
|
|
1278
|
-
"additionalDirectories.relatedMountRuntimeUnsupported": "이 머신의 Happy CLI가 관련 프로젝트 자동 마운트를 지원하지 않아 기본 세션으로 시작했습니다.",
|
|
1279
|
-
"additionalDirectories.relatedMountLimit": "관련 프로젝트 디렉터리 상한을 넘어 {count}개를 제외하고 세션을 시작했습니다."
|
|
1508
|
+
...additionalDirectoryMountWarningKo
|
|
1280
1509
|
};
|
|
1281
1510
|
const agentHandoffKo = {
|
|
1282
1511
|
"agentHandoff.open": "다른 에이전트로 이어가기",
|
|
@@ -1357,9 +1586,28 @@ const chatFolderUploadKo = {
|
|
|
1357
1586
|
"upload.partialIssue": "업로드 실패 {failed}개 · 건너뜀 {skipped}개",
|
|
1358
1587
|
"upload.invalidPath": "안전하지 않은 첨부 경로입니다."
|
|
1359
1588
|
};
|
|
1589
|
+
const composerAddMenuKo = {
|
|
1590
|
+
"composer.addMenu.trigger": "추가",
|
|
1591
|
+
"composer.addMenu.fileOrPhoto": "파일 또는 사진 추가",
|
|
1592
|
+
"composer.addMenu.folder": "폴더 추가",
|
|
1593
|
+
"composer.addMenu.disabled": "지금은 파일이나 폴더를 추가할 수 없습니다.",
|
|
1594
|
+
"shortcuts.action.composerAttachFile": "파일 또는 사진 추가",
|
|
1595
|
+
"shortcuts.action.composerAttachFileDescription": "포커스된 메시지 작성란의 파일 선택기를 엽니다."
|
|
1596
|
+
};
|
|
1597
|
+
const composerQuickCommandsKo = {
|
|
1598
|
+
"composer.quickCommands.trigger": "Quick Commands",
|
|
1599
|
+
"composer.quickCommands.saveCurrent": "현재 입력을 이 프로젝트의 Quick Command로 저장",
|
|
1600
|
+
"composer.quickCommands.saveCurrentGlobal": "현재 입력을 모든 프로젝트의 Quick Command로 저장",
|
|
1601
|
+
"composer.quickCommands.storageWriteFailed": "이 기기에 저장하지 못했습니다 — 앱을 다시 시작하면 목록이 사라질 수 있습니다.",
|
|
1602
|
+
"composer.quickCommands.empty": "저장된 Quick Command가 없습니다.",
|
|
1603
|
+
"composer.quickCommands.projectScope": "이 프로젝트",
|
|
1604
|
+
"composer.quickCommands.globalScope": "모든 프로젝트",
|
|
1605
|
+
"composer.quickCommands.remove": '"{label}" 삭제'
|
|
1606
|
+
};
|
|
1360
1607
|
const aiCommitPrKo = {
|
|
1361
1608
|
"aiCommitPr.stagedCount": "스테이지된 파일 {count}개",
|
|
1362
1609
|
"aiCommitPr.noStaged": "스테이지된 변경이 없습니다.",
|
|
1610
|
+
"aiCommitPr.stagedUnknown": "스테이지된 변경을 확인하지 못했습니다.",
|
|
1363
1611
|
"aiCommitPr.stageAll": "모든 변경 스테이지",
|
|
1364
1612
|
"aiCommitPr.staging": "스테이지하는 중…",
|
|
1365
1613
|
"aiCommitPr.messagePlaceholder": "커밋 메시지",
|
|
@@ -1436,8 +1684,188 @@ const connectorReadinessKo = {
|
|
|
1436
1684
|
"connectorReadiness.state.configMissing": "설정 없음",
|
|
1437
1685
|
"connectorReadiness.state.unknown": "상태 미확인"
|
|
1438
1686
|
};
|
|
1439
|
-
const
|
|
1687
|
+
const checkpointKo = {
|
|
1688
|
+
"checkpoint.activation.label": "파일 체크포인트 보호",
|
|
1689
|
+
"checkpoint.activation.title": "에이전트가 파일을 변경하기 전에 세션별 체크포인트를 만듭니다.",
|
|
1690
|
+
"checkpoint.activation.hint": "세션별",
|
|
1691
|
+
"checkpoint.activation.unavailable.platform": "macOS에서만 지원",
|
|
1692
|
+
"checkpoint.activation.unavailable.agent": "Claude와 Codex만 지원",
|
|
1693
|
+
"checkpoint.activation.unavailable.sandbox": "프로젝트 샌드박스 필요",
|
|
1694
|
+
"checkpoint.activation.unavailable.worktree": "작업 사본은 준비 중",
|
|
1695
|
+
"checkpoint.activation.error": "파일 체크포인트 보호를 사용할 수 없습니다: {reason}",
|
|
1696
|
+
"checkpoint.title": "파일 체크포인트",
|
|
1697
|
+
"checkpoint.status.protected": "보호됨",
|
|
1698
|
+
"checkpoint.status.legacy": "보호되지 않음",
|
|
1699
|
+
"checkpoint.status.unavailable": "보호 사용 불가",
|
|
1700
|
+
"checkpoint.refresh": "새로고침",
|
|
1701
|
+
"checkpoint.timeline": "기록",
|
|
1702
|
+
"checkpoint.timeline.empty": "아직 체크포인트가 없습니다.",
|
|
1703
|
+
"checkpoint.timeline.snapshot": "스냅샷",
|
|
1704
|
+
"checkpoint.timeline.rewind": "복원",
|
|
1705
|
+
"checkpoint.preview": "미리보기",
|
|
1706
|
+
"checkpoint.preview.title": "복원 미리보기",
|
|
1707
|
+
"checkpoint.preview.empty": "되돌릴 파일 변경이 없습니다.",
|
|
1708
|
+
"checkpoint.preview.confirm": "이 미리보기의 파일 변경을 확인했습니다",
|
|
1709
|
+
"checkpoint.restore": "파일 상태 복원",
|
|
1710
|
+
"checkpoint.restore.running": "파일 상태를 복원하는 중…",
|
|
1711
|
+
"checkpoint.restore.completed": "파일 상태 복원이 완료되었습니다.",
|
|
1712
|
+
"checkpoint.restore.partial": "일부 파일을 복원하지 못했습니다.",
|
|
1713
|
+
"checkpoint.restore.stale": "파일 상태가 바뀌었습니다. 새 미리보기를 확인하세요.",
|
|
1714
|
+
"checkpoint.restore.retry": "실패한 복원 다시 시도",
|
|
1715
|
+
"checkpoint.restore.cancel": "복원 취소",
|
|
1716
|
+
"checkpoint.pending.title": "보호 정책 확인 필요",
|
|
1717
|
+
"checkpoint.pending.partialWarning": "이번 turn이 일부 실행되었을 수 있습니다.",
|
|
1718
|
+
"checkpoint.pending.externalWarning": "외부 부작용은 다시 실행될 수 있습니다.",
|
|
1719
|
+
"checkpoint.pending.cancel": "보호 유지하고 취소",
|
|
1720
|
+
"checkpoint.pending.disable": "보호 해제",
|
|
1721
|
+
"checkpoint.restartRequired": "계속하려면 daemon 세션을 재시작해야 합니다.",
|
|
1722
|
+
"checkpoint.restart": "보호 없이 세션 재시작",
|
|
1723
|
+
"checkpoint.restart.running": "세션을 재시작하는 중…",
|
|
1724
|
+
"checkpoint.error.action": "체크포인트 작업을 완료하지 못했습니다.",
|
|
1725
|
+
"checkpoint.action.restore": "되돌리기",
|
|
1726
|
+
"checkpoint.action.delete": "삭제",
|
|
1727
|
+
"checkpoint.action.skip": "보존",
|
|
1728
|
+
"checkpoint.action.conflict": "충돌",
|
|
1729
|
+
"checkpoint.failure.mutation-failed": "파일 변경 실패",
|
|
1730
|
+
"checkpoint.failure.mutation-outcome-unknown": "결과를 알 수 없어 재확인이 필요함"
|
|
1731
|
+
};
|
|
1732
|
+
const localLoopKo = {
|
|
1733
|
+
"preview.reasonNoProject": "선택된 프로젝트가 없습니다.",
|
|
1734
|
+
"preview.reasonNoPort": "프로젝트에 미리보기 포트가 설정되어 있지 않습니다.",
|
|
1735
|
+
"preview.reasonNoMachine": "프로젝트가 머신에 연결되어 있지 않습니다.",
|
|
1736
|
+
"preview.reasonMachineMissing": "프로젝트의 머신을 찾을 수 없습니다.",
|
|
1737
|
+
"preview.reasonMachineOffline": "{host} 머신이 오프라인입니다.",
|
|
1738
|
+
"preview.reasonWorktreeNotRunning": "worktree 미리보기가 아직 실행 중이 아닙니다.",
|
|
1739
|
+
"preview.reasonWorktreeMachineOffline": "worktree 미리보기 머신이 오프라인입니다.",
|
|
1740
|
+
"preview.standaloneNoPortHint": "로컬 모드에서는 터미널이나 에이전트로 dev 서버를 실행하면 프로젝트 안에서 열린 포트를 감지해 여기에 표시합니다.",
|
|
1741
|
+
"workCompletion.commitNoRemote": "원격 저장소가 없어 push와 PR 생성 없이 커밋까지만 요청합니다.",
|
|
1742
|
+
"workCompletion.commitOnlyAction": "바로 커밋",
|
|
1743
|
+
"projectSettings.workspaceDefaultPlaceholder": "기본 경로: {path}",
|
|
1744
|
+
"preview.standaloneHostNotEmbeddable": "{host} 은(는) 앱 안에 표시할 수 없는 주소입니다. 이 컴퓨터(127.0.0.1)나 .local 호스트만 직접 표시합니다."
|
|
1745
|
+
};
|
|
1746
|
+
const localizationGapKo = {
|
|
1747
|
+
"systemPrompt.loading": "프롬프트를 불러오는 중...",
|
|
1748
|
+
"systemPrompt.presetsAria": "시스템 프롬프트 프리셋",
|
|
1749
|
+
"systemPrompt.instructionsLabel": "지시문",
|
|
1750
|
+
"systemPrompt.placeholder": "데스크탑 세션에 적용할 사용자 지시문",
|
|
1751
|
+
"systemPrompt.charCount": "{count}자",
|
|
1752
|
+
"projectInfo.projectId": "프로젝트 ID",
|
|
1753
|
+
"projectInfo.workspace": "작업 폴더",
|
|
1754
|
+
"projectInfo.devCommand": "개발 서버 명령",
|
|
1755
|
+
"projectInfo.machinePlatform": "머신 플랫폼",
|
|
1756
|
+
"projectInfo.electron": "Electron 버전",
|
|
1757
|
+
"projectInfo.unknown": "알 수 없음",
|
|
1758
|
+
"projectInfo.notConfigured": "설정되지 않음",
|
|
1759
|
+
"projectInfo.unbound": "연결된 머신 없음",
|
|
1760
|
+
"projectMeta.source": "소스",
|
|
1761
|
+
"projectMeta.preview": "미리보기",
|
|
1762
|
+
"projectMeta.machineUnbound": "미지정",
|
|
1763
|
+
"projectMeta.previewUnavailable": "사용 불가",
|
|
1764
|
+
"machineDialog.eyebrow": "세션 대상",
|
|
1765
|
+
"machineDialog.title": "머신 선택",
|
|
1766
|
+
"machineDialog.cancelAria": "머신 선택 취소",
|
|
1767
|
+
"machineDialog.unknownPlatform": "플랫폼 미확인",
|
|
1768
|
+
"newProject.repoInitFailed": "프로젝트는 만들었지만 Git 저장소 초기화에 실패했습니다: {detail}",
|
|
1769
|
+
"standalone.defaultProjectName": "로컬 프로젝트"
|
|
1770
|
+
};
|
|
1771
|
+
const settingsInfoStructureKo = {
|
|
1772
|
+
"about.mode": "모드",
|
|
1773
|
+
"about.mode.demo": "데모 워크스페이스",
|
|
1774
|
+
"about.mode.local": "로컬 워크스페이스",
|
|
1775
|
+
"about.mode.guest": "로컬 워크스페이스 (Guest)",
|
|
1776
|
+
"about.mode.cloud": "Saycode 계정 워크스페이스",
|
|
1777
|
+
"about.appVersion": "앱 버전",
|
|
1778
|
+
"about.userData": "사용자 데이터",
|
|
1779
|
+
"about.unknown": "알 수 없음",
|
|
1780
|
+
"about.updates": "업데이트",
|
|
1781
|
+
"about.versionLine": "현재 {current} · 최신 {latest}",
|
|
1782
|
+
"about.check": "업데이트 확인",
|
|
1783
|
+
"about.download": "업데이트 다운로드",
|
|
1784
|
+
"about.installRestart": "설치 후 재시작",
|
|
1785
|
+
"about.status.checking": "업데이트를 확인하는 중…",
|
|
1786
|
+
"about.status.available": "{version} 버전을 사용할 수 있습니다.",
|
|
1787
|
+
"about.status.downloading": "업데이트 다운로드 중 {percent}%",
|
|
1788
|
+
"about.status.downloaded": "업데이트를 내려받았습니다. 재시작하면 설치됩니다.",
|
|
1789
|
+
"about.status.latest": "최신 버전을 사용 중입니다.",
|
|
1790
|
+
"about.status.unsupported": "업데이트는 패키징된 macOS 빌드에서 제공됩니다.",
|
|
1791
|
+
"about.status.error": "업데이트 확인에 실패했습니다.",
|
|
1792
|
+
"about.status.idle": "업데이트는 자동으로 확인됩니다.",
|
|
1793
|
+
"homeCard.owner": "소유자",
|
|
1794
|
+
"homeCard.branch": "브랜치",
|
|
1795
|
+
"homeCard.updated": "업데이트",
|
|
1796
|
+
"homeCard.preview": "프리뷰",
|
|
1797
|
+
"homeCard.previewAvailable": "사용 가능",
|
|
1798
|
+
"homeCard.previewNotReady": "준비 안 됨",
|
|
1799
|
+
"homeCard.deployed": "배포 시각",
|
|
1800
|
+
"theme.title": "테마",
|
|
1801
|
+
"theme.help": "자동은 macOS 시스템 설정을 따릅니다. 홈 화면 오른쪽 위 토글과 같은 설정입니다.",
|
|
1802
|
+
"theme.auto": "자동",
|
|
1803
|
+
"theme.light": "라이트",
|
|
1804
|
+
"theme.dark": "다크",
|
|
1805
|
+
"theme.autoTitle": "시스템 설정 따르기",
|
|
1806
|
+
"theme.lightTitle": "라이트 모드",
|
|
1807
|
+
"theme.darkTitle": "다크 모드",
|
|
1808
|
+
"standaloneConnectivity.localServer": "로컬 happy-server",
|
|
1809
|
+
"standaloneConnectivity.zrokShare": "zrok 공유 주소",
|
|
1810
|
+
"standaloneConnectivity.zrokFixedName": "zrok 고정 이름",
|
|
1811
|
+
"memoryLayerPanel.remoteNotice": "메모리 레이어 작업은 선택한 프로젝트의 원격 머신에서 실행됩니다.",
|
|
1812
|
+
"memoryLayerPanel.remoteNoticeWithMachine": "메모리 레이어 작업은 선택한 프로젝트의 원격 머신({machine})에서 실행됩니다.",
|
|
1813
|
+
"memoryLayerPanel.targetUnavailable": "원격 머신 대상을 사용할 수 없습니다.",
|
|
1814
|
+
"memoryLayerPanel.checking": "claude-memory-layer를 확인하는 중…",
|
|
1815
|
+
"memoryLayerPanel.installed": "설치됨",
|
|
1816
|
+
"memoryLayerPanel.missing": "미설치",
|
|
1817
|
+
"memoryLayerPanel.updateAvailable": "업데이트 가능",
|
|
1818
|
+
"memoryLayerPanel.version": "버전 {version}",
|
|
1819
|
+
"memoryLayerPanel.versionUnknown": "버전 알 수 없음",
|
|
1820
|
+
"memoryLayerPanel.latest": "최신 {version}",
|
|
1821
|
+
"memoryLayerPanel.notFound": "찾을 수 없음",
|
|
1822
|
+
"memoryLayerPanel.unknown": "알 수 없음",
|
|
1823
|
+
"memoryLayerPanel.notAssigned": "지정되지 않음",
|
|
1824
|
+
"memoryLayerPanel.remoteMachine": "원격 머신",
|
|
1825
|
+
"memoryLayerPanel.installGlobally": "전역 설치",
|
|
1826
|
+
"memoryLayerPanel.updatePackage": "패키지 업데이트",
|
|
1827
|
+
"memoryLayerPanel.reinstallHooks": "훅 다시 설치",
|
|
1828
|
+
"memoryLayerPanel.installing": "설치하는 중…",
|
|
1829
|
+
"memoryLayerPanel.installed.notice": "원격 머신에 claude-memory-layer가 준비됐습니다.",
|
|
1830
|
+
"memoryLayerPanel.agentsTitle": "MCP 에이전트",
|
|
1831
|
+
"memoryLayerPanel.detected": "감지됨",
|
|
1832
|
+
"memoryLayerPanel.registered": "등록됨",
|
|
1833
|
+
"memoryLayerPanel.register": "등록",
|
|
1834
|
+
"memoryLayerPanel.registering": "등록하는 중…",
|
|
1835
|
+
"memoryLayerPanel.registrationUpdated": "{agent} MCP 등록을 원격 머신에 반영했습니다.",
|
|
1836
|
+
"memoryLayerPanel.noAgents": "에이전트 상태를 확인할 수 없습니다.",
|
|
1837
|
+
"memoryLayerPanel.dashboardTitle": "프로젝트 대시보드",
|
|
1838
|
+
"memoryLayerPanel.project": "프로젝트",
|
|
1839
|
+
"memoryLayerPanel.noProject": "선택한 프로젝트 없음",
|
|
1840
|
+
"memoryLayerPanel.path": "경로",
|
|
1841
|
+
"memoryLayerPanel.machine": "머신",
|
|
1842
|
+
"memoryLayerPanel.dashboard": "대시보드",
|
|
1843
|
+
"memoryLayerPanel.remotePort": "원격 포트 {port}",
|
|
1844
|
+
"memoryLayerPanel.notRunning": "실행 중 아님",
|
|
1845
|
+
"memoryLayerPanel.open": "열기",
|
|
1846
|
+
"memoryLayerPanel.stop": "중지",
|
|
1847
|
+
"memoryLayerPanel.starting": "시작하는 중…",
|
|
1848
|
+
"memoryLayerPanel.startDashboard": "대시보드 시작",
|
|
1849
|
+
"memoryLayerPanel.dashboardStarted": "원격 메모리 대시보드를 시작했습니다.",
|
|
1850
|
+
"memoryLayerPanel.dashboardStopped": "원격 메모리 대시보드를 중지했습니다.",
|
|
1851
|
+
"memoryLayerPanel.lastOutput": "마지막 명령 출력",
|
|
1852
|
+
"machineConsole.ownerMe": "나",
|
|
1853
|
+
"overflowMenu.aria": "추가 액션 메뉴",
|
|
1854
|
+
"overflowMenu.title": "추가 액션"
|
|
1855
|
+
};
|
|
1856
|
+
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, ...hubSessionKo };
|
|
1440
1857
|
const onboardingKo = {
|
|
1858
|
+
"boot.stage.app": "앱 준비 중…",
|
|
1859
|
+
"boot.stage.localRuntime": "로컬 작업공간 준비 중…",
|
|
1860
|
+
"login.dialogTitle": "Saycode 계정으로 로그인",
|
|
1861
|
+
"login.dialogDescription": "로컬 데이터는 이 컴퓨터에 그대로 남고, 현재 창만 클라우드 작업공간으로 전환됩니다.",
|
|
1862
|
+
"login.dialogClose": "로그인 닫기",
|
|
1863
|
+
"guestRecovery.title": "로컬 작업공간을 준비하지 못했어요",
|
|
1864
|
+
"guestRecovery.body": "Saycode가 로컬 런타임을 시작하지 못했습니다. 다시 시도하거나 계정으로 로그인해 클라우드 작업공간을 사용하세요.",
|
|
1865
|
+
"guestRecovery.retry": "다시 시도",
|
|
1866
|
+
"guestRecovery.retrying": "로컬 작업공간 준비 중…",
|
|
1867
|
+
"account.guest": "게스트",
|
|
1868
|
+
"account.localWorkspace": "로컬 작업공간",
|
|
1441
1869
|
"onboarding.dialogAria": "시작 설정",
|
|
1442
1870
|
"onboarding.eyebrow": "처음 시작하기",
|
|
1443
1871
|
"onboarding.title": "첫 작업까지 함께 준비해요",
|
|
@@ -1460,9 +1888,9 @@ const onboardingKo = {
|
|
|
1460
1888
|
"onboarding.oneCommand.advanced": "고급: 수동 설치",
|
|
1461
1889
|
"onboarding.localMachine.registering": "이 컴퓨터를 등록하는 중…",
|
|
1462
1890
|
"onboarding.localMachine.registered": "✓ 이 컴퓨터가 등록되었습니다. 바로 작업할 수 있어요.",
|
|
1463
|
-
"onboarding.localMachine.askTitle": "
|
|
1464
|
-
"onboarding.localMachine.askBody": "
|
|
1465
|
-
"onboarding.localMachine.askAction": "
|
|
1891
|
+
"onboarding.localMachine.askTitle": "Saycode CLI 설치",
|
|
1892
|
+
"onboarding.localMachine.askBody": "버튼을 누르면 이 컴퓨터의 기존 설정을 안전하게 백업한 뒤 Saycode CLI를 설치하고 연결합니다.",
|
|
1893
|
+
"onboarding.localMachine.askAction": "Saycode CLI 설치",
|
|
1466
1894
|
"onboarding.localMachine.failed": "자동 등록에 실패했습니다. 아래 명령으로 직접 설정할 수 있어요.",
|
|
1467
1895
|
"onboarding.localMachine.retry": "다시 시도",
|
|
1468
1896
|
"onboarding.localMachine.remoteHint": "다른 컴퓨터나 조직 공유 머신은 머신 설정에서 추가할 수 있어요.",
|
|
@@ -1485,7 +1913,101 @@ const onboardingKo = {
|
|
|
1485
1913
|
"onboarding.refresh": "상태 다시 확인",
|
|
1486
1914
|
"onboarding.allDone": "준비가 끝났습니다. 이제 첫 작업을 시작할 수 있어요.",
|
|
1487
1915
|
"onboarding.resume": "시작 설정 이어하기",
|
|
1488
|
-
"onboarding.preflight": "{provider}를 사용하기 전에 로그인을 완료해 주세요."
|
|
1916
|
+
"onboarding.preflight": "{provider}를 사용하기 전에 로그인을 완료해 주세요.",
|
|
1917
|
+
"onboarding.page.happy": "Saycode CLI",
|
|
1918
|
+
"onboarding.page.agents": "AI 도구",
|
|
1919
|
+
"onboarding.page.notifications": "알림",
|
|
1920
|
+
"onboarding.page.happyTitle": "Saycode CLI 준비",
|
|
1921
|
+
"onboarding.page.happyBody": "설치와 버전을 확인하고, 필요한 작업만 이 화면에서 실행합니다.",
|
|
1922
|
+
"onboarding.page.agentsTitle": "Claude Code와 Codex",
|
|
1923
|
+
"onboarding.page.agentsBody": "사용할 AI 도구를 설치하고 계정 로그인을 완료하세요.",
|
|
1924
|
+
"onboarding.page.notificationsTitle": "알림 설정",
|
|
1925
|
+
"onboarding.page.notificationsBody": "응답 요청과 작업 완료를 놓치지 않도록 알림과 사운드를 설정하세요.",
|
|
1926
|
+
"onboarding.page.back": "이전",
|
|
1927
|
+
"onboarding.page.continue": "계속",
|
|
1928
|
+
"onboarding.page.addProject": "첫 프로젝트 추가",
|
|
1929
|
+
"onboarding.page.configured": "설정됨",
|
|
1930
|
+
"onboarding.page.optional": "선택",
|
|
1931
|
+
"onboarding.page.testNotification": "테스트 알림 보내기",
|
|
1932
|
+
"onboarding.page.testNotificationSending": "테스트 알림 보내는 중…",
|
|
1933
|
+
"onboarding.page.testNotificationSuccess": "테스트 알림을 보냈습니다.",
|
|
1934
|
+
"onboarding.page.testNotificationUnsupported": "이 환경에서는 OS 알림을 사용할 수 없습니다.",
|
|
1935
|
+
"onboarding.page.testNotificationFailed": "테스트 알림을 보내지 못했습니다.",
|
|
1936
|
+
"onboarding.page.testNotificationTitle": "Saycode 알림 테스트",
|
|
1937
|
+
"onboarding.page.testNotificationBody": "알림이 잘 도착하고 있어요.",
|
|
1938
|
+
"onboarding.cli.installed": "설치됨",
|
|
1939
|
+
"onboarding.cli.standaloneBody": "Standalone 앱에 포함된 로컬 런타임을 사용합니다.",
|
|
1940
|
+
"onboarding.cli.currentBody": "이 컴퓨터의 Saycode CLI가 최신 버전입니다.",
|
|
1941
|
+
"onboarding.cli.installTitle": "이 컴퓨터에 CLI가 필요합니다",
|
|
1942
|
+
"onboarding.cli.installBody": "Saycode가 설치, 계정 연결, 최종 확인을 순서대로 진행합니다.",
|
|
1943
|
+
"onboarding.cli.installAction": "Saycode CLI 설치",
|
|
1944
|
+
"onboarding.cli.installing": "설치 중…",
|
|
1945
|
+
"onboarding.cli.verifying": "설치 상태 확인 중…",
|
|
1946
|
+
"onboarding.cli.stage.prepare": "설치 준비",
|
|
1947
|
+
"onboarding.cli.stage.install": "CLI 설치 및 연결",
|
|
1948
|
+
"onboarding.cli.stage.verify": "설치 상태 확인",
|
|
1949
|
+
"onboarding.cli.updateTitle": "업데이트가 필요합니다",
|
|
1950
|
+
"onboarding.cli.updateAction": "업데이트 실행",
|
|
1951
|
+
"onboarding.cli.updating": "업데이트 후 설치 상태를 확인하는 중…",
|
|
1952
|
+
"onboarding.cli.checking": "설치 상태 확인 중…",
|
|
1953
|
+
"onboarding.cli.checkFailedTitle": "설치 상태를 확인하지 못했습니다",
|
|
1954
|
+
"onboarding.cli.checkFailedBody": "연결을 확인한 뒤 다시 시도해 주세요.",
|
|
1955
|
+
"onboarding.cli.retry": "다시 확인",
|
|
1956
|
+
"onboarding.cli.nav.checking": "확인 중",
|
|
1957
|
+
"onboarding.cli.nav.install-required": "설치 필요",
|
|
1958
|
+
"onboarding.cli.nav.installing": "설치 중",
|
|
1959
|
+
"onboarding.cli.nav.installed": "설치됨",
|
|
1960
|
+
"onboarding.cli.nav.update-required": "업데이트 필요",
|
|
1961
|
+
"onboarding.cli.nav.updating": "업데이트 중",
|
|
1962
|
+
"onboarding.cli.nav.failed": "확인 필요",
|
|
1963
|
+
"onboarding.provider.checking": "확인 중",
|
|
1964
|
+
"onboarding.provider.connected": "연결됨",
|
|
1965
|
+
"onboarding.provider.install-required": "설치 필요",
|
|
1966
|
+
"onboarding.provider.login-required": "로그인 필요",
|
|
1967
|
+
"onboarding.provider.failed": "확인 실패",
|
|
1968
|
+
"onboarding.provider.needsConnection": "연결 필요",
|
|
1969
|
+
"firstProject.title": "프로젝트 추가",
|
|
1970
|
+
"firstProject.description": "Saycode를 시작하려면 프로젝트를 추가하세요.",
|
|
1971
|
+
"firstProject.close": "프로젝트 추가 창 닫기",
|
|
1972
|
+
"firstProject.host": "호스트",
|
|
1973
|
+
"firstProject.selectHost": "호스트 선택",
|
|
1974
|
+
"firstProject.noHost": "사용 가능한 머신 없음",
|
|
1975
|
+
"firstProject.thisComputer": "이 컴퓨터",
|
|
1976
|
+
"firstProject.remoteHost": "원격 머신",
|
|
1977
|
+
"firstProject.online": "온라인",
|
|
1978
|
+
"firstProject.offline": "오프라인",
|
|
1979
|
+
"firstProject.addMachine": "다른 머신 추가",
|
|
1980
|
+
"firstProject.addMachineHint": "원격 머신 또는 공유 머신 연결",
|
|
1981
|
+
"firstProject.startMethod": "프로젝트 시작 방법",
|
|
1982
|
+
"firstProject.otherWays": "프로젝트를 추가하는 다른 방법",
|
|
1983
|
+
"firstProject.source.existing-folder.title": "기존 폴더 열기",
|
|
1984
|
+
"firstProject.source.existing-folder.description": "이 컴퓨터나 선택한 머신의 폴더를 사용합니다.",
|
|
1985
|
+
"firstProject.source.zip.title": "ZIP으로 가져오기",
|
|
1986
|
+
"firstProject.source.zip.description": "압축 파일에서 로컬 프로젝트를 만듭니다.",
|
|
1987
|
+
"firstProject.source.git.title": "Git URL에서 복제",
|
|
1988
|
+
"firstProject.source.git.description": "원격 Git 저장소를 선택한 머신에 복제합니다.",
|
|
1989
|
+
"firstProject.source.blank.title": "새 프로젝트 만들기",
|
|
1990
|
+
"firstProject.source.blank.description": "빈 폴더에서 새 프로젝트를 시작합니다.",
|
|
1991
|
+
"firstProject.form.back": "이전",
|
|
1992
|
+
"firstProject.form.machine": "프로젝트를 만들 머신",
|
|
1993
|
+
"firstProject.form.blank.title": "새 프로젝트",
|
|
1994
|
+
"firstProject.form.blank.description": "이름을 정하면 로컬에 새 프로젝트를 만듭니다.",
|
|
1995
|
+
"firstProject.form.git.title": "코드 저장소 가져오기",
|
|
1996
|
+
"firstProject.form.git.description": "Git 주소를 입력하면 이 컴퓨터에 저장소를 복제합니다.",
|
|
1997
|
+
"firstProject.form.existing-folder.title": "기존 폴더 사용",
|
|
1998
|
+
"firstProject.form.existing-folder.description": "로컬 폴더를 Saycode 프로젝트로 연결합니다.",
|
|
1999
|
+
"firstProject.form.zip.title": "ZIP으로 가져오기",
|
|
2000
|
+
"firstProject.form.zip.description": "압축 파일을 선택하면 프로젝트 이름을 자동으로 정합니다.",
|
|
2001
|
+
"firstProject.form.zip.pick": "ZIP 파일 선택",
|
|
2002
|
+
"firstProject.form.zip.drop": "ZIP 파일을 선택하거나 여기로 드래그하세요",
|
|
2003
|
+
"guestRecovery.detailsSummary": "진단 정보 보기",
|
|
2004
|
+
"onboarding.cli.globalInstallTitle": "터미널에서도 Saycode CLI 사용하기",
|
|
2005
|
+
"onboarding.cli.globalInstallBody": "선택 사항입니다. npm으로 전역 happy 명령을 설치합니다: npm i -g @buzzni/happy-cli",
|
|
2006
|
+
"onboarding.cli.globalInstallAction": "전역 CLI 설치",
|
|
2007
|
+
"onboarding.cli.globalInstalling": "설치 중… 몇 분 걸릴 수 있습니다",
|
|
2008
|
+
"onboarding.cli.globalInstalled": "전역 CLI 설치됨 ({version})",
|
|
2009
|
+
"onboarding.cli.globalInstallFailed": "전역 CLI 설치 실패: {detail}",
|
|
2010
|
+
"onboarding.cli.globalInstalledUnversioned": "전역 CLI 설치됨"
|
|
1489
2011
|
};
|
|
1490
2012
|
const standaloneRemoteOnboardingKo = {
|
|
1491
2013
|
"onboarding.optional": " (선택)",
|
|
@@ -1596,7 +2118,7 @@ const ko = {
|
|
|
1596
2118
|
"happyCliUpdate.cancel": "취소",
|
|
1597
2119
|
// Boot screen
|
|
1598
2120
|
"boot.preparing": "Saycode 준비 중...",
|
|
1599
|
-
"boot.stage.credentials": "
|
|
2121
|
+
"boot.stage.credentials": "로그인 상태 확인 중…",
|
|
1600
2122
|
"boot.stage.workspace": "워크스페이스 불러오는 중…",
|
|
1601
2123
|
// Language selection (first run)
|
|
1602
2124
|
"language.select.title": "언어를 선택하세요",
|
|
@@ -1661,8 +2183,8 @@ const ko = {
|
|
|
1661
2183
|
"zipImport.transferFailed": "{count}개 파일을 전송하지 못했습니다. ({path}: {reason})",
|
|
1662
2184
|
"zipImport.machineInfoMissing": "머신 정보(homeDir/workspaceRoot)가 부족해 가져올 위치를 결정할 수 없습니다.",
|
|
1663
2185
|
"zipImport.emptyArchive": "압축 파일 안에 가져올 파일이 없습니다.",
|
|
1664
|
-
"sidebarPanel.showAllChats": "
|
|
1665
|
-
"sidebarPanel.showAllChatsAria": "{project}
|
|
2186
|
+
"sidebarPanel.showAllChats": "대화 더보기 ({count}개 남음)",
|
|
2187
|
+
"sidebarPanel.showAllChatsAria": "{project}에서 대화 더 보기",
|
|
1666
2188
|
"sidebarPanel.newChatAria": "{name} 프로젝트에서 새 대화 시작",
|
|
1667
2189
|
"home.eyebrow": "워크스페이스 홈",
|
|
1668
2190
|
"home.newProject": "새 프로젝트",
|
|
@@ -1717,7 +2239,6 @@ const ko = {
|
|
|
1717
2239
|
"newProject.gitNeedUrl": "Git 저장소 주소를 입력해 주세요.",
|
|
1718
2240
|
"newProject.importDone": "'{name}' 프로젝트에 파일 {count}개를 가져왔습니다.",
|
|
1719
2241
|
"newProject.importFailed": "'{name}' 프로젝트는 생성했지만 파일을 가져오지 못했습니다. {detail}",
|
|
1720
|
-
"composer.attachFile": "파일 첨부",
|
|
1721
2242
|
"composer.attachNeedMachine": "첨부하려면 머신 연결이 필요합니다",
|
|
1722
2243
|
"composer.attachNoMachine": "파일을 업로드할 머신을 찾을 수 없습니다.",
|
|
1723
2244
|
"composer.attachUploadFailed": "파일을 업로드하지 못했습니다.",
|
|
@@ -1744,7 +2265,7 @@ const ko = {
|
|
|
1744
2265
|
"workspace.orgAdminMenuEntry": "조직 관리",
|
|
1745
2266
|
// Org admin console (specs/desktop-org-admin-console)
|
|
1746
2267
|
"orgAdmin.title": "조직 관리",
|
|
1747
|
-
"orgAdmin.eyebrow": "
|
|
2268
|
+
"orgAdmin.eyebrow": "조직",
|
|
1748
2269
|
"orgAdmin.close": "닫기",
|
|
1749
2270
|
"orgAdmin.tab.members": "멤버 관리",
|
|
1750
2271
|
"orgAdmin.tab.settings": "조직 설정",
|
|
@@ -1773,7 +2294,7 @@ const ko = {
|
|
|
1773
2294
|
"orgAdmin.settingsSaved": "조직 설정이 저장되었습니다.",
|
|
1774
2295
|
"orgAdmin.tab.audit": "감사 로그",
|
|
1775
2296
|
"orgAdmin.tab.machineMonitor": "머신 모니터링",
|
|
1776
|
-
"orgAdmin.tab.skill": "
|
|
2297
|
+
"orgAdmin.tab.skill": "스킬",
|
|
1777
2298
|
"orgAdmin.tab.projects": "프로젝트",
|
|
1778
2299
|
"orgAdmin.skillSearch": "Skill 검색",
|
|
1779
2300
|
"orgAdmin.skillSearchPlaceholder": "Skill 또는 설명 검색...",
|
|
@@ -1888,7 +2409,7 @@ const ko = {
|
|
|
1888
2409
|
"orgAdmin.githubOwnerPlaceholder": "예: myteam",
|
|
1889
2410
|
"orgAdmin.label": "레이블 (선택)",
|
|
1890
2411
|
"orgAdmin.labelPlaceholder": "Main Account, Backup Account 등",
|
|
1891
|
-
"orgAdmin.githubPat": "
|
|
2412
|
+
"orgAdmin.githubPat": "개인 액세스 토큰",
|
|
1892
2413
|
"orgAdmin.githubPatRequired": "PAT를 입력하세요.",
|
|
1893
2414
|
"orgAdmin.githubPatPlaceholder": "github_pat_... 형식의 PAT",
|
|
1894
2415
|
"orgAdmin.githubPatUnchanged": "변경 없음 (기존 PAT 유지)",
|
|
@@ -2011,7 +2532,7 @@ const ko = {
|
|
|
2011
2532
|
// Super admin console (specs/desktop-org-admin-console)
|
|
2012
2533
|
"systemAdmin.menuEntry": "슈퍼 관리자",
|
|
2013
2534
|
"systemAdmin.title": "슈퍼 관리자",
|
|
2014
|
-
"systemAdmin.eyebrow": "
|
|
2535
|
+
"systemAdmin.eyebrow": "시스템",
|
|
2015
2536
|
"systemAdmin.companiesTitle": "전체 조직",
|
|
2016
2537
|
"systemAdmin.createCompany": "조직 생성",
|
|
2017
2538
|
"systemAdmin.create": "생성",
|
|
@@ -2086,7 +2607,7 @@ const ko = {
|
|
|
2086
2607
|
"panel.recovery.loginDesc": "로그인 후 터미널을 시작할 수 있습니다.",
|
|
2087
2608
|
// Chat composer
|
|
2088
2609
|
"composer.messageAria": "메시지",
|
|
2089
|
-
"composer.placeholder": "
|
|
2610
|
+
"composer.placeholder": "메시지를 입력하세요",
|
|
2090
2611
|
"composer.promptSuggestionPaused": "{window} 사용량 {percent}% — 사용량 경고 중에는 Claude가 추천 질문을 만들지 않습니다",
|
|
2091
2612
|
"composer.promptSuggestionPausedReset": "{reset} 초기화",
|
|
2092
2613
|
"composer.starting": "시작 중...",
|
|
@@ -2139,18 +2660,20 @@ const ko = {
|
|
|
2139
2660
|
"sessionWorktree.branch": "브랜치: {branch}",
|
|
2140
2661
|
"sessionWorktree.path": "경로: {path}",
|
|
2141
2662
|
"sessionWorktree.merged": "병합됨",
|
|
2663
|
+
"chatPullRequest.open": "{repository} PR #{number} 열기",
|
|
2142
2664
|
// Project git controls
|
|
2143
2665
|
"gitControls.branchLoading": "Git branch 확인 중",
|
|
2144
2666
|
"gitControls.noRemote": "origin remote 또는 branch를 찾을 수 없습니다.",
|
|
2145
2667
|
"gitControls.checkFailed": "Git 확인 실패",
|
|
2668
|
+
"gitControls.retryStatus": "다시 확인",
|
|
2146
2669
|
"gitControls.checking": "Git 확인 중",
|
|
2147
2670
|
"gitControls.manageBranchAria": "{branch} branch 관리",
|
|
2148
2671
|
"gitControls.manageBranchTitle": "Branch 변경 및 생성",
|
|
2149
2672
|
"gitControls.manageBranchTitleWithDesc": "Branch 변경 및 생성 · {description}",
|
|
2150
2673
|
"gitControls.pullAria": "{branch} 브랜치 pull",
|
|
2151
|
-
"gitControls.pullTitle": "{branch} pull",
|
|
2674
|
+
"gitControls.pullTitle": "{branch} 가져오기(pull)",
|
|
2152
2675
|
"gitControls.pushAria": "{branch} 브랜치 push",
|
|
2153
|
-
"gitControls.pushTitle": "{branch} push",
|
|
2676
|
+
"gitControls.pushTitle": "{branch} 올리기(push)",
|
|
2154
2677
|
"gitControls.manageWorktreeAria": "{name} worktree 관리",
|
|
2155
2678
|
"gitControls.manageWorktreeTitle": "Worktree 관리",
|
|
2156
2679
|
"gitControls.operationResultDialogTitle": "{label} 결과",
|
|
@@ -2595,6 +3118,12 @@ const ko = {
|
|
|
2595
3118
|
// Owner workspace sync (team share) messages
|
|
2596
3119
|
"ownerSync.hint.credentialsUnavailable": "원격 머신 / 인증 정보가 없어요. 프로젝트 메뉴에서 머신을 한 번 연결한 뒤 다시 시도해 주세요.",
|
|
2597
3120
|
"ownerSync.successConnectionUpdated": "저장소 연결을 변경했습니다.",
|
|
3121
|
+
"ownerSync.deferred.workspaceDirNotFound": "저장소 연결은 완료됐지만 워크스페이스 폴더를 찾지 못해 코드는 아직 저장하지 않았어요. 프로젝트를 연 뒤 다시 저장해 주세요.",
|
|
3122
|
+
"ownerSync.deferred.workspaceEmpty": "저장소 연결은 완료됐지만 저장할 파일이 없어 코드는 아직 저장하지 않았어요. 파일을 만든 뒤 다시 저장해 주세요.",
|
|
3123
|
+
"ownerSync.deferred.credentialsUnavailable": "저장소 연결은 완료됐지만 머신 자격 증명을 사용할 수 없어 코드는 아직 저장하지 않았어요. 자격 증명을 확인한 뒤 다시 저장해 주세요.",
|
|
3124
|
+
"ownerSync.deferred.machineHomeUnresolvable": "저장소 연결은 완료됐지만 머신 홈 폴더를 확인할 수 없어 코드는 아직 저장하지 않았어요. 머신 홈 폴더를 확인한 뒤 다시 저장해 주세요.",
|
|
3125
|
+
"ownerSync.deferred.machineUnavailable": "저장소 연결은 완료됐지만 머신이 오프라인이라 코드는 아직 저장하지 않았어요. 머신을 다시 연결한 뒤 저장해 주세요.",
|
|
3126
|
+
"ownerSync.deferred.default": "저장소 연결은 완료됐지만 코드는 아직 저장하지 않았어요. 프로젝트와 머신 상태를 확인한 뒤 다시 저장해 주세요.",
|
|
2598
3127
|
"ownerSync.hint.machineHomeUnresolvable": "원격 머신을 찾을 수 없어요. happy daemon 이 실행 중인지 확인해 주세요.",
|
|
2599
3128
|
"ownerSync.hint.machineUnavailable": "원격 머신 daemon 이 오프라인이에요. happy daemon 을 시작한 뒤 다시 시도해 주세요.",
|
|
2600
3129
|
"ownerSync.hint.missingTools": "원격 머신에 git / tar 가 설치되어 있지 않아요. 운영자에게 설치 요청을 보내주세요.",
|
|
@@ -2857,6 +3386,22 @@ const ko = {
|
|
|
2857
3386
|
"community.cloneFailed": "커뮤니티 프로젝트를 받아오지 못했습니다.",
|
|
2858
3387
|
"login.credentialsRequired": "이메일과 비밀번호를 입력해주세요.",
|
|
2859
3388
|
"login.loginFailed": "로그인에 실패했습니다.",
|
|
3389
|
+
"login.mfaTitle": "추가 인증",
|
|
3390
|
+
"login.mfaLead": "이 계정은 2단계 인증이 필요합니다. 인증 앱의 6자리 코드를 입력해주세요.",
|
|
3391
|
+
"login.mfaMethodsLead": "이 계정은 2단계 인증이 필요합니다. 패스키 또는 등록한 인증 앱으로 인증해주세요.",
|
|
3392
|
+
"login.mfaPasskey": "패스키로 인증",
|
|
3393
|
+
"login.mfaPasskeyRetry": "패스키 인증 다시 열기",
|
|
3394
|
+
"login.mfaPasskeyWaiting": "시스템 브라우저에서 iPhone 또는 이 Mac의 패스키 인증을 완료해주세요.",
|
|
3395
|
+
"login.mfaTotpFallback": "브라우저를 닫아도 인증 앱 코드로 계속할 수 있습니다.",
|
|
3396
|
+
"login.mfaCodeLabel": "인증 코드",
|
|
3397
|
+
"login.mfaCodePlaceholder": "6자리 숫자",
|
|
3398
|
+
"login.mfaSubmit": "인증하고 로그인",
|
|
3399
|
+
"login.mfaCancel": "다른 계정으로 로그인",
|
|
3400
|
+
"login.mfaInvalidCode": "인증 코드가 올바르지 않습니다. 다시 시도해주세요.",
|
|
3401
|
+
"login.mfaExpired": "인증 시간이 만료되었습니다. 다시 로그인해주세요.",
|
|
3402
|
+
"login.mfaRateLimited": "시도가 너무 많습니다. 잠시 후 다시 시도해주세요.",
|
|
3403
|
+
"login.mfaUnsupported": "이 계정에서 사용할 수 있는 2단계 인증 방법이 없습니다. 웹에서 인증 앱 또는 패스키를 등록한 뒤 다시 시도해주세요.",
|
|
3404
|
+
"login.mfaPasskeyFailed": "패스키 인증을 시작하지 못했습니다. 다시 시도하거나 인증 앱 코드를 사용해주세요.",
|
|
2860
3405
|
"standalone.startFailed": "로컬 standalone 서버를 시작하지 못했습니다.",
|
|
2861
3406
|
"workspace.loadFailed": "워크스페이스를 불러오지 못했습니다.",
|
|
2862
3407
|
"standalone.modeStartFailed": "로컬 standalone 모드를 시작하지 못했습니다.",
|
|
@@ -3044,6 +3589,12 @@ const ko = {
|
|
|
3044
3589
|
"worktreeOp.merging": "머지 중...",
|
|
3045
3590
|
"worktreeOp.mergeFailed": "머지에 실패했습니다.",
|
|
3046
3591
|
"worktreeOp.mergeDone": "머지 완료. 이제 worktree를 정리할 수 있습니다.",
|
|
3592
|
+
"worktreeOp.demoAutoMergeBlocked": "데모 모드에서는 auto-merge를 사용할 수 없습니다.",
|
|
3593
|
+
"worktreeOp.autoMergeEnabling": "auto-merge 켜는 중...",
|
|
3594
|
+
"worktreeOp.autoMergeDisabling": "auto-merge 끄는 중...",
|
|
3595
|
+
"worktreeOp.autoMergeEnabled": "auto-merge가 켜졌습니다. 머지 조건(체크·필수 리뷰 등)을 모두 충족하면 자동으로 머지됩니다.",
|
|
3596
|
+
"worktreeOp.autoMergeDisabled": "auto-merge가 꺼졌습니다.",
|
|
3597
|
+
"worktreeOp.autoMergeFailed": "auto-merge 설정을 바꾸지 못했습니다.",
|
|
3047
3598
|
"worktreeOp.checkingStatus": "worktree 상태 확인 중...",
|
|
3048
3599
|
"worktreeOp.forceDeleteUnpushed": "worktree에 푸시되지 않은 커밋 {count}개가 있습니다. 강제로 삭제할까요? 커밋이 사라집니다.",
|
|
3049
3600
|
"worktreeOp.forceDeleteDirty": "worktree에 정리되지 않은 변경이 있습니다. 강제로 삭제할까요? 변경 사항이 사라집니다.",
|
|
@@ -3163,6 +3714,9 @@ const ko = {
|
|
|
3163
3714
|
"preview.tokenUrlInvalid": "Preview 토큰 URL이 올바르지 않습니다.",
|
|
3164
3715
|
"branchDialog.loading": "Branch 목록을 불러오는 중입니다.",
|
|
3165
3716
|
"branchDialog.empty": "표시할 branch가 없습니다.",
|
|
3717
|
+
"branchDialog.searchLabel": "브랜치 검색",
|
|
3718
|
+
"branchDialog.searchPlaceholder": "브랜치 이름 검색",
|
|
3719
|
+
"branchDialog.searchEmpty": "'{query}'와 일치하는 브랜치가 없습니다.",
|
|
3166
3720
|
"branchDialog.actionsAria": "{name} 브랜치 작업",
|
|
3167
3721
|
"branchDialog.rowAria": "{name} {meta}",
|
|
3168
3722
|
"branchDialog.switch": "브랜치 이동",
|
|
@@ -3177,6 +3731,24 @@ const ko = {
|
|
|
3177
3731
|
"branchDialog.deleteConfirm": "'{name}' 브랜치를 삭제할까요? 머지되지 않은 커밋이 있으면 Git이 거부합니다.",
|
|
3178
3732
|
"branchDialog.deleteConfirmAction": "삭제",
|
|
3179
3733
|
"branchDialog.cancel": "취소",
|
|
3734
|
+
"branchCleanup.openAria": "병합된 브랜치 정리",
|
|
3735
|
+
"branchCleanup.title": "정리 가능한 브랜치",
|
|
3736
|
+
"branchCleanup.loading": "브랜치를 확인하는 중…",
|
|
3737
|
+
"branchCleanup.empty": "정리 가능한 브랜치가 없습니다.",
|
|
3738
|
+
"branchCleanup.scannedNote": "로컬 브랜치 {total}개 중 {count}개가 정리 가능합니다.",
|
|
3739
|
+
"branchCleanup.selectAll": "모두 선택",
|
|
3740
|
+
"branchCleanup.deselectAll": "모두 해제",
|
|
3741
|
+
"branchCleanup.deleteSelected": "{count}개 삭제",
|
|
3742
|
+
"branchCleanup.deleteFailed": "브랜치를 삭제할 수 없습니다.",
|
|
3743
|
+
"branchCleanup.deletePartial": "{total}개 중 {failed}개를 삭제하지 못했습니다: {detail}",
|
|
3744
|
+
"branchCleanup.loadFailed": "브랜치 확인을 실패했습니다.",
|
|
3745
|
+
"branchCleanup.noBaseBranch": "기본 브랜치를 찾을 수 없습니다. origin/HEAD 를 설정하거나 프로젝트 설정에서 기본 브랜치를 지정하세요.",
|
|
3746
|
+
"branchCleanup.probeIncomplete": "브랜치 정보를 완전히 읽지 못했습니다. 목록이 비어 보일 수 있으니 다시 시도하세요.",
|
|
3747
|
+
"branchCleanup.demoUnsupported": "Demo 모드에서는 지원하지 않습니다.",
|
|
3748
|
+
"branchCleanup.refreshAria": "브랜치 목록 새로고침",
|
|
3749
|
+
"branchCleanup.squashScanning": "squash merge된 브랜치를 찾는 중…",
|
|
3750
|
+
"branchCleanup.squashFailed": "squash merge 검사에 실패했습니다. 아래 목록은 일반 merge 후보만입니다.",
|
|
3751
|
+
"branchCleanup.squashTag": "squash 병합",
|
|
3180
3752
|
"message.deleteConfirm": "이 메시지를 삭제할까요? 이 작업은 새로고침 후에도 유지됩니다.",
|
|
3181
3753
|
"message.deleteFailed": "메시지를 삭제할 수 없습니다.",
|
|
3182
3754
|
"message.sendFailed": "메시지를 보낼 수 없습니다.",
|
|
@@ -3665,6 +4237,8 @@ const ko = {
|
|
|
3665
4237
|
// ProjectDocumentViewer
|
|
3666
4238
|
"inlinePreview.openInViewer": "파일 뷰어로 열기",
|
|
3667
4239
|
"inlinePreview.loading": "미리보기를 불러오는 중",
|
|
4240
|
+
"inlinePreview.loadFailed": "미리보기를 불러오지 못했습니다.",
|
|
4241
|
+
"inlinePreview.retry": "다시 시도",
|
|
3668
4242
|
"documentViewer.docxConvertFailed": "DOCX 문서를 변환하지 못했습니다.",
|
|
3669
4243
|
"documentViewer.pdfViewer": "{name} PDF 뷰어",
|
|
3670
4244
|
"documentViewer.pdfPreparing": "PDF를 준비하는 중...",
|
|
@@ -3715,7 +4289,7 @@ const ko = {
|
|
|
3715
4289
|
"connectors.browserHint": "브라우저에서 승인을 마친 뒤 앱으로 돌아오세요.",
|
|
3716
4290
|
"connectors.availableNextTurn": "연결됐습니다. 다음 메시지부터 사용할 수 있습니다.",
|
|
3717
4291
|
"connectors.readOnlyNote": "읽기 전용으로만 연결합니다.",
|
|
3718
|
-
"connectors.apiKeyLabel": "KNOI API
|
|
4292
|
+
"connectors.apiKeyLabel": "KNOI API 키",
|
|
3719
4293
|
"connectors.apiKeyEnter": "API Key 입력",
|
|
3720
4294
|
"connectors.apiKeyReplace": "API Key 교체",
|
|
3721
4295
|
"connectors.apiKeySave": "API Key 저장",
|
|
@@ -3728,7 +4302,7 @@ const ko = {
|
|
|
3728
4302
|
"remoteFile.saveFailed": "파일을 저장하지 못했습니다.",
|
|
3729
4303
|
"fileDownload.invalidData": "다운로드할 파일 데이터가 올바르지 않습니다.",
|
|
3730
4304
|
"fileDownload.tooLarge": "다운로드 파일은 100 MiB 이하만 지원합니다.",
|
|
3731
|
-
"codex.needsLatestCli": "
|
|
4305
|
+
"codex.needsLatestCli": "선택한 모델을 사용하려면 최신 Codex CLI가 필요합니다. `npm install -g @openai/codex@latest`로 업데이트한 뒤 새 대화를 시작해 주세요.",
|
|
3732
4306
|
"codex.error": "Codex 오류: {detail}",
|
|
3733
4307
|
// specs/desktop-error-surface — 실패 알림의 계층 배지와 복구 액션
|
|
3734
4308
|
"errorSurface.layer.provider": "Provider 오류",
|
|
@@ -3858,7 +4432,7 @@ const ko = {
|
|
|
3858
4432
|
"settingsTab.network": "네트워크 데이터 절약",
|
|
3859
4433
|
"settingsTab.env": "환경 변수",
|
|
3860
4434
|
"settingsTab.security": "보안",
|
|
3861
|
-
"settingsTab.memory": "
|
|
4435
|
+
"settingsTab.memory": "메모리",
|
|
3862
4436
|
"settingsTab.connectors": "커넥터",
|
|
3863
4437
|
"settingsTab.myAiCredentials": "내 AI 계정",
|
|
3864
4438
|
"settingsTab.extensions": "확장",
|
|
@@ -3891,7 +4465,9 @@ const ko = {
|
|
|
3891
4465
|
"extensions.developmentSource": "개발 빌드: {directory}",
|
|
3892
4466
|
"extensions.apiDeprecated": "업데이트 필요",
|
|
3893
4467
|
"extensions.apiDeprecatedHint": "이전 확장 API({apiVersion})로 동작 중이며, 다음 업데이트에서 지원이 끝납니다.",
|
|
3894
|
-
"extensions.
|
|
4468
|
+
"extensions.enablement.enabled": "활성화됨",
|
|
4469
|
+
"extensions.enablement.disabled": "비활성화됨",
|
|
4470
|
+
"extensions.runtime.inactive": "대기 중",
|
|
3895
4471
|
"extensions.runtime.active": "실행 중",
|
|
3896
4472
|
"extensions.runtime.quarantined": "격리됨",
|
|
3897
4473
|
"shortcuts.title": "단축키",
|
|
@@ -3984,7 +4560,7 @@ const ko = {
|
|
|
3984
4560
|
"review.self.disabledWaiting": "먼저 대기 중인 질문이나 권한 요청에 응답해주세요.",
|
|
3985
4561
|
"review.self.rounds": "반복 횟수",
|
|
3986
4562
|
"review.self.roundsHint": "medium 이상 지적이 나오면 남은 횟수만큼 리뷰를 자동으로 이어갑니다. low·nit만 남으면 바로 끝냅니다.",
|
|
3987
|
-
"review.self.appOpenHint": "
|
|
4563
|
+
"review.self.appOpenHint": "상시 실행 에이전트가 최신 버전이면 앱을 완전히 종료해도 계속됩니다. 지원하지 않는 환경에서는 앱이 켜져 있어야 합니다.",
|
|
3988
4564
|
"review.self.loopActive": "반복 리뷰 {round}/{total} 진행 중",
|
|
3989
4565
|
"review.self.loopStop": "반복 중지",
|
|
3990
4566
|
"review.self.loopDismiss": "기록 지우기",
|
|
@@ -3995,7 +4571,7 @@ const ko = {
|
|
|
3995
4571
|
"review.self.loopReason.roundsExhausted": "지정한 횟수를 모두 사용",
|
|
3996
4572
|
"review.self.loopReason.unstructured": "결과를 판정할 수 없어 중단",
|
|
3997
4573
|
"review.self.loopReason.sendFailed": "다음 라운드를 보내지 못함",
|
|
3998
|
-
"review.self.loopReason.cancelled": "
|
|
4574
|
+
"review.self.loopReason.cancelled": "사용자 요청으로 중단",
|
|
3999
4575
|
"review.history.title": "독립 리뷰 기록",
|
|
4000
4576
|
"review.storage.writeFailed": "이 기기에 리뷰 기록을 저장하지 못했습니다. 앱을 다시 켜면 이 목록이 비어 있을 수 있습니다.",
|
|
4001
4577
|
"review.recovery.title": "기록 복구",
|
|
@@ -4055,7 +4631,7 @@ const ko = {
|
|
|
4055
4631
|
"review.error.sourceProjectMissing": "리뷰 원본 프로젝트를 찾을 수 없습니다.",
|
|
4056
4632
|
"review.error.sourceSessionMissing": "리뷰 원본 대화를 찾을 수 없습니다.",
|
|
4057
4633
|
// Project processes (specs/desktop-process-ownership R4)
|
|
4058
|
-
"projectProcesses.eyebrow": "
|
|
4634
|
+
"projectProcesses.eyebrow": "실행 중인 프로세스",
|
|
4059
4635
|
"projectProcesses.dialogAria": "{name} 프로세스 보기",
|
|
4060
4636
|
"projectProcesses.closeAria": "프로세스 보기 닫기",
|
|
4061
4637
|
"projectProcesses.listAria": "{name}의 실행 중인 프로세스 목록",
|
|
@@ -4233,6 +4809,7 @@ function interruptDecision(state) {
|
|
|
4233
4809
|
return state.phase === "idle" ? "exit-cli" : "open-menu";
|
|
4234
4810
|
}
|
|
4235
4811
|
const AGENT_UNTIL_VALUES = ["turn-end", "waiting-input", "completed"];
|
|
4812
|
+
const STATE_WINDOW_MESSAGES = 50;
|
|
4236
4813
|
const DEFAULT_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4237
4814
|
const MAX_WAIT_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
4238
4815
|
function isAgentUntil(value2) {
|
|
@@ -4433,6 +5010,12 @@ function collectFlags(argv, startIndex) {
|
|
|
4433
5010
|
flags.last = takeNumber(argv, index, token);
|
|
4434
5011
|
index += 1;
|
|
4435
5012
|
break;
|
|
5013
|
+
case "--status":
|
|
5014
|
+
flags.status = true;
|
|
5015
|
+
break;
|
|
5016
|
+
case "--any":
|
|
5017
|
+
flags.any = true;
|
|
5018
|
+
break;
|
|
4436
5019
|
case "--wait":
|
|
4437
5020
|
flags.wait = true;
|
|
4438
5021
|
break;
|
|
@@ -4588,12 +5171,13 @@ Options
|
|
|
4588
5171
|
|
|
4589
5172
|
Agent commands (machine-readable JSON on stdout, exit 3 on failure)
|
|
4590
5173
|
agent whoami Show this session's contact point and limits
|
|
4591
|
-
agent ls
|
|
4592
|
-
agent read <session> [--last n]
|
|
5174
|
+
agent ls [--status] List sessions in scope; --status adds state/pending/cursor
|
|
5175
|
+
agent read <session> [--last n] [--since seq] Recent messages + completion signal
|
|
4593
5176
|
agent wait <session> --until <state> turn-end | waiting-input | completed
|
|
5177
|
+
agent wait --any <s1> <s2>... --until <state> Block until any of them holds it
|
|
4594
5178
|
agent prompt <session> <text> [--wait] Send a prompt, optionally wait for it
|
|
4595
|
-
agent steer <session> <text> Redirect an
|
|
4596
|
-
agent stop <session> Stop an
|
|
5179
|
+
agent steer <session> <text> Redirect an active response (depth 0: any in scope)
|
|
5180
|
+
agent stop <session> Stop an active response (depth 0: any in scope)
|
|
4597
5181
|
agent spawn --prompt <text> [--agent n] [--model n] [--effort n]
|
|
4598
5182
|
Start a sibling with an explicit execution choice
|
|
4599
5183
|
agent spawn --prompt <text> --file <path> ...with context files it reads first
|
|
@@ -6983,6 +7567,89 @@ function bounded(value2, maxLength) {
|
|
|
6983
7567
|
if (typeof value2 !== "string" || value2.length === 0 || value2.length > maxLength) return void 0;
|
|
6984
7568
|
return value2;
|
|
6985
7569
|
}
|
|
7570
|
+
function boundedAllowEmpty(value2, maxLength) {
|
|
7571
|
+
if (typeof value2 !== "string" || value2.length > maxLength) return void 0;
|
|
7572
|
+
return value2;
|
|
7573
|
+
}
|
|
7574
|
+
function parsePasswordLoginResponse(raw) {
|
|
7575
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw) && raw.mfaRequired === true) {
|
|
7576
|
+
const value2 = raw;
|
|
7577
|
+
const loginToken = bounded(value2.loginToken, 512);
|
|
7578
|
+
if (loginToken === void 0) throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
|
|
7579
|
+
const methods = Array.isArray(value2.methods) ? value2.methods.filter((method) => typeof method === "string") : [];
|
|
7580
|
+
return {
|
|
7581
|
+
kind: "mfa-required",
|
|
7582
|
+
challenge: {
|
|
7583
|
+
loginToken,
|
|
7584
|
+
...typeof value2.expiresAt === "string" ? { expiresAt: value2.expiresAt } : {},
|
|
7585
|
+
methods,
|
|
7586
|
+
// 형이 어긋나면 보수적으로 "등록 필요" — 코드 입력을 헛되이 시키지 않는다.
|
|
7587
|
+
enrollmentRequired: value2.enrollmentRequired !== false
|
|
7588
|
+
}
|
|
7589
|
+
};
|
|
7590
|
+
}
|
|
7591
|
+
return { kind: "success", result: parseCloudLoginResult(raw) };
|
|
7592
|
+
}
|
|
7593
|
+
function parseKdfParams(value2) {
|
|
7594
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7595
|
+
const entries = Object.entries(value2);
|
|
7596
|
+
if (entries.length > 8) return void 0;
|
|
7597
|
+
const parsed = {};
|
|
7598
|
+
for (const [key, entry] of entries) {
|
|
7599
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(key)) return void 0;
|
|
7600
|
+
if (typeof entry === "string") {
|
|
7601
|
+
if (entry.length > 128) return void 0;
|
|
7602
|
+
} else if (typeof entry !== "number" && typeof entry !== "boolean") return void 0;
|
|
7603
|
+
parsed[key] = entry;
|
|
7604
|
+
}
|
|
7605
|
+
return parsed;
|
|
7606
|
+
}
|
|
7607
|
+
function parseUnlockMethod(value2) {
|
|
7608
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7609
|
+
const method = value2;
|
|
7610
|
+
const id = bounded(method.id, 128);
|
|
7611
|
+
const type = method.type;
|
|
7612
|
+
const kdfAlgorithm = bounded(method.kdfAlgorithm, 128);
|
|
7613
|
+
const kdfParams = parseKdfParams(method.kdfParams);
|
|
7614
|
+
const salt = boundedAllowEmpty(method.salt, 256);
|
|
7615
|
+
const wrappedMasterKey = bounded(method.wrappedMasterKey, 16384);
|
|
7616
|
+
const possessionPublicKey = bounded(method.possessionPublicKey, 43);
|
|
7617
|
+
if (!id || !/^[A-Za-z0-9_-]+$/.test(id) || type !== "password" && type !== "recovery" && type !== "device" || !kdfAlgorithm || !kdfParams || salt === void 0 || !wrappedMasterKey) return void 0;
|
|
7618
|
+
const revision = method.revision;
|
|
7619
|
+
if (revision !== void 0 && (!Number.isSafeInteger(revision) || revision < 1)) return void 0;
|
|
7620
|
+
return {
|
|
7621
|
+
id,
|
|
7622
|
+
type,
|
|
7623
|
+
kdfAlgorithm,
|
|
7624
|
+
kdfParams,
|
|
7625
|
+
salt,
|
|
7626
|
+
wrappedMasterKey,
|
|
7627
|
+
...possessionPublicKey && /^[A-Za-z0-9_-]{43}$/.test(possessionPublicKey) ? { possessionPublicKey } : {},
|
|
7628
|
+
...revision === void 0 ? {} : { revision }
|
|
7629
|
+
};
|
|
7630
|
+
}
|
|
7631
|
+
function parseBundledAccountKey(value2) {
|
|
7632
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return void 0;
|
|
7633
|
+
const payload = value2;
|
|
7634
|
+
if (payload.mode !== "bundled" || !payload.material || typeof payload.material !== "object") return void 0;
|
|
7635
|
+
const material = payload.material;
|
|
7636
|
+
const keyVersion = material.keyVersion;
|
|
7637
|
+
const publicKey = bounded(material.publicKey, 128);
|
|
7638
|
+
const encryptedPrivateKey = bounded(material.encryptedPrivateKey, 16384);
|
|
7639
|
+
if (!Number.isSafeInteger(keyVersion) || keyVersion < 1 || !publicKey || !encryptedPrivateKey || !Array.isArray(material.unlockMethods) || material.unlockMethods.length > 16) return void 0;
|
|
7640
|
+
return {
|
|
7641
|
+
mode: "bundled",
|
|
7642
|
+
material: {
|
|
7643
|
+
keyVersion,
|
|
7644
|
+
publicKey,
|
|
7645
|
+
encryptedPrivateKey,
|
|
7646
|
+
unlockMethods: material.unlockMethods.flatMap((method) => {
|
|
7647
|
+
const parsed = parseUnlockMethod(method);
|
|
7648
|
+
return parsed ? [parsed] : [];
|
|
7649
|
+
})
|
|
7650
|
+
}
|
|
7651
|
+
};
|
|
7652
|
+
}
|
|
6986
7653
|
function parseCloudLoginResult(raw) {
|
|
6987
7654
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6988
7655
|
throw new Error("CLOUD_LOGIN_RESPONSE_INVALID");
|
|
@@ -6995,15 +7662,58 @@ function parseCloudLoginResult(raw) {
|
|
|
6995
7662
|
};
|
|
6996
7663
|
const accountPublicKey = bounded(value2.accountPublicKey, 128);
|
|
6997
7664
|
const accountKeySalt = bounded(value2.accountKeySalt, 128);
|
|
7665
|
+
const mfaSessionToken = bounded(value2.mfaSessionToken, 512);
|
|
7666
|
+
const accountKey = parseBundledAccountKey(value2.accountKey);
|
|
6998
7667
|
return {
|
|
6999
7668
|
userId: required("userId", 256),
|
|
7000
7669
|
username: required("username", 256),
|
|
7001
7670
|
token: required("token", 8192),
|
|
7002
7671
|
secret: required("secret", 8192),
|
|
7672
|
+
...mfaSessionToken ? { mfaSessionToken } : {},
|
|
7003
7673
|
...accountPublicKey ? { accountPublicKey } : {},
|
|
7004
|
-
...accountKeySalt ? { accountKeySalt } : {}
|
|
7674
|
+
...accountKeySalt ? { accountKeySalt } : {},
|
|
7675
|
+
...accountKey ? { accountKey } : {}
|
|
7676
|
+
};
|
|
7677
|
+
}
|
|
7678
|
+
const HANDOFF_ID_PATTERN = /^mph-[A-Za-z0-9_-]{22}$/;
|
|
7679
|
+
const CAPABILITY_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
7680
|
+
function record$5(value2) {
|
|
7681
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7682
|
+
}
|
|
7683
|
+
function validExpiry(value2) {
|
|
7684
|
+
return typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0;
|
|
7685
|
+
}
|
|
7686
|
+
function parseMfaPasskeyHandoff(raw, apiBaseUrl) {
|
|
7687
|
+
const value2 = record$5(raw);
|
|
7688
|
+
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)) {
|
|
7689
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7690
|
+
}
|
|
7691
|
+
let authorizationUrl;
|
|
7692
|
+
try {
|
|
7693
|
+
authorizationUrl = new URL(value2.authorizationUrl);
|
|
7694
|
+
const expectedOrigin = new URL(apiBaseUrl).origin;
|
|
7695
|
+
const fragmentParams = new URLSearchParams(authorizationUrl.hash.slice(1));
|
|
7696
|
+
const fragmentToken = fragmentParams.get("browserToken");
|
|
7697
|
+
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)) {
|
|
7698
|
+
throw new Error("invalid");
|
|
7699
|
+
}
|
|
7700
|
+
} catch {
|
|
7701
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7702
|
+
}
|
|
7703
|
+
return {
|
|
7704
|
+
handoffId: value2.handoffId,
|
|
7705
|
+
authorizationUrl: authorizationUrl.toString(),
|
|
7706
|
+
exchangeToken: value2.exchangeToken,
|
|
7707
|
+
expiresAt: value2.expiresAt
|
|
7005
7708
|
};
|
|
7006
7709
|
}
|
|
7710
|
+
function parsePendingMfaPasskeyExchange(raw) {
|
|
7711
|
+
const value2 = record$5(raw);
|
|
7712
|
+
if (!value2 || value2.status !== "pending" || !validExpiry(value2.expiresAt)) {
|
|
7713
|
+
throw new Error("MFA_PASSKEY_HANDOFF_RESPONSE_INVALID");
|
|
7714
|
+
}
|
|
7715
|
+
return { kind: "pending", expiresAt: value2.expiresAt };
|
|
7716
|
+
}
|
|
7007
7717
|
const ENVELOPE_VERSION = 0;
|
|
7008
7718
|
const EPHEMERAL_PUBLIC_KEY_LENGTH = 32;
|
|
7009
7719
|
const ENVELOPE_NONCE_LENGTH = 24;
|
|
@@ -7048,6 +7758,19 @@ async function decryptDataKeyToString(dataEncryptionKey, bundleBase64) {
|
|
|
7048
7758
|
return null;
|
|
7049
7759
|
}
|
|
7050
7760
|
}
|
|
7761
|
+
async function decryptDataKeyJson(dataEncryptionKey, bundleBase64) {
|
|
7762
|
+
const json = await decryptDataKeyToString(dataEncryptionKey, bundleBase64);
|
|
7763
|
+
if (json === null) return null;
|
|
7764
|
+
try {
|
|
7765
|
+
return JSON.parse(json);
|
|
7766
|
+
} catch {
|
|
7767
|
+
return null;
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
async function decryptDataKeyRecord(dataEncryptionKey, bundleBase64) {
|
|
7771
|
+
const value2 = await decryptDataKeyJson(dataEncryptionKey, bundleBase64);
|
|
7772
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
|
|
7773
|
+
}
|
|
7051
7774
|
async function encryptDataKeyContent(dataEncryptionKey, value2) {
|
|
7052
7775
|
const nonce = nacl.randomBytes(GCM_NONCE_LENGTH);
|
|
7053
7776
|
const key = await importGcmKey(dataEncryptionKey);
|
|
@@ -7059,28 +7782,64 @@ async function encryptDataKeyContent(dataEncryptionKey, value2) {
|
|
|
7059
7782
|
bundle.set(ciphertextWithTag, 1 + GCM_NONCE_LENGTH);
|
|
7060
7783
|
return encodeBase64(bundle);
|
|
7061
7784
|
}
|
|
7785
|
+
let candidates = [];
|
|
7786
|
+
let version = 0;
|
|
7787
|
+
function dataKeyUnwrapCandidates(accountPrivateKey, companyKeys = getCompanyKeyCandidates()) {
|
|
7788
|
+
return accountPrivateKey ? [accountPrivateKey, ...companyKeys] : [];
|
|
7789
|
+
}
|
|
7790
|
+
function getCompanyKeyCandidates() {
|
|
7791
|
+
return candidates.map((candidate) => candidate.privateKey);
|
|
7792
|
+
}
|
|
7793
|
+
function companyKeyChainVersion() {
|
|
7794
|
+
return version;
|
|
7795
|
+
}
|
|
7062
7796
|
const dekBySessionId = /* @__PURE__ */ new Map();
|
|
7063
7797
|
const lockedSessionIds = /* @__PURE__ */ new Set();
|
|
7064
7798
|
const fingerprints = /* @__PURE__ */ new Map();
|
|
7799
|
+
const companyUnwrappedEnvelopes = /* @__PURE__ */ new Map();
|
|
7800
|
+
function firstUnwrap(envelope, candidates2) {
|
|
7801
|
+
for (const candidate of candidates2) {
|
|
7802
|
+
const dek = unwrapDataEncryptionKey(envelope, candidate);
|
|
7803
|
+
if (dek) return dek;
|
|
7804
|
+
}
|
|
7805
|
+
return null;
|
|
7806
|
+
}
|
|
7807
|
+
let boundaryGeneration$1 = 0;
|
|
7808
|
+
function sessionDekRegistryGeneration() {
|
|
7809
|
+
return boundaryGeneration$1;
|
|
7810
|
+
}
|
|
7811
|
+
function adoptSessionDek(sessionId, envelope, dek, byCompanyKey) {
|
|
7812
|
+
const existing = dekBySessionId.get(sessionId);
|
|
7813
|
+
if (existing && existing.length === dek.length && existing.every((byte, index) => byte === dek[index])) dek.fill(0);
|
|
7814
|
+
else dekBySessionId.set(sessionId, dek);
|
|
7815
|
+
lockedSessionIds.delete(sessionId);
|
|
7816
|
+
if (byCompanyKey) companyUnwrappedEnvelopes.set(sessionId, envelope);
|
|
7817
|
+
else companyUnwrappedEnvelopes.delete(sessionId);
|
|
7818
|
+
}
|
|
7065
7819
|
function registerSessionDek(session, accountPrivateKey) {
|
|
7066
7820
|
const envelope = session.dataEncryptionKey ?? null;
|
|
7067
7821
|
if (!envelope) {
|
|
7068
7822
|
dekBySessionId.delete(session.id);
|
|
7069
7823
|
lockedSessionIds.delete(session.id);
|
|
7070
7824
|
fingerprints.delete(session.id);
|
|
7825
|
+
companyUnwrappedEnvelopes.delete(session.id);
|
|
7071
7826
|
return;
|
|
7072
7827
|
}
|
|
7073
|
-
const fingerprint = `${envelope}|${accountPrivateKey ? "1" : "0"}`;
|
|
7828
|
+
const fingerprint = `${envelope}|${accountPrivateKey ? "1" : "0"}|${companyKeyChainVersion()}`;
|
|
7074
7829
|
if (fingerprints.get(session.id) === fingerprint) return;
|
|
7830
|
+
if (!accountPrivateKey && dekBySessionId.has(session.id) && fingerprints.get(session.id)?.startsWith(`${envelope}|1|`)) return;
|
|
7831
|
+
const previousFingerprint = fingerprints.get(session.id);
|
|
7075
7832
|
fingerprints.set(session.id, fingerprint);
|
|
7076
|
-
const
|
|
7833
|
+
const accountDek = accountPrivateKey ? unwrapDataEncryptionKey(envelope, accountPrivateKey) : null;
|
|
7834
|
+
const dek = accountDek ?? (accountPrivateKey ? firstUnwrap(envelope, getCompanyKeyCandidates()) : null);
|
|
7077
7835
|
if (dek) {
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
} else {
|
|
7081
|
-
dekBySessionId.delete(session.id);
|
|
7082
|
-
lockedSessionIds.add(session.id);
|
|
7836
|
+
adoptSessionDek(session.id, envelope, dek, accountDek === null);
|
|
7837
|
+
return;
|
|
7083
7838
|
}
|
|
7839
|
+
if (dekBySessionId.has(session.id) && previousFingerprint?.startsWith(`${envelope}|`)) return;
|
|
7840
|
+
dekBySessionId.delete(session.id);
|
|
7841
|
+
lockedSessionIds.add(session.id);
|
|
7842
|
+
if (companyUnwrappedEnvelopes.has(session.id)) companyUnwrappedEnvelopes.set(session.id, envelope);
|
|
7084
7843
|
}
|
|
7085
7844
|
function getSessionDek(sessionId) {
|
|
7086
7845
|
return dekBySessionId.get(sessionId) ?? null;
|
|
@@ -7105,21 +7864,77 @@ function cacheSet(store, sessionId, ciphertext, plaintext) {
|
|
|
7105
7864
|
function getCachedDataKeyMetadataJson(sessionId, ciphertext) {
|
|
7106
7865
|
return cacheGet(headerCache, sessionId, ciphertext);
|
|
7107
7866
|
}
|
|
7108
|
-
async function preDecryptSessionHeaders(sessions, accountPrivateKey) {
|
|
7867
|
+
async function preDecryptSessionHeaders(sessions, accountPrivateKey, sinceGeneration = sessionDekRegistryGeneration()) {
|
|
7868
|
+
const boundaryIntact = () => sessionDekRegistryGeneration() === sinceGeneration;
|
|
7109
7869
|
await Promise.all(sessions.map(async (session) => {
|
|
7870
|
+
if (!boundaryIntact()) return;
|
|
7110
7871
|
registerSessionDek(session, accountPrivateKey);
|
|
7111
7872
|
const dek = getSessionDek(session.id);
|
|
7112
7873
|
if (!dek) return;
|
|
7113
7874
|
if (session.metadata && getCachedDataKeyMetadataJson(session.id, session.metadata) === null) {
|
|
7114
7875
|
const json = await decryptDataKeyToString(dek, session.metadata);
|
|
7115
|
-
if (json !== null) cacheSet(headerCache, session.id, session.metadata, json);
|
|
7876
|
+
if (json !== null && boundaryIntact()) cacheSet(headerCache, session.id, session.metadata, json);
|
|
7116
7877
|
}
|
|
7117
7878
|
if (session.agentState && getCachedDataKeyMetadataJson(session.id, session.agentState) === null) {
|
|
7118
7879
|
const json = await decryptDataKeyToString(dek, session.agentState);
|
|
7119
|
-
if (json !== null) cacheSet(headerCache, session.id, session.agentState, json);
|
|
7880
|
+
if (json !== null && boundaryIntact()) cacheSet(headerCache, session.id, session.agentState, json);
|
|
7120
7881
|
}
|
|
7121
7882
|
}));
|
|
7122
7883
|
}
|
|
7884
|
+
const machineDekById = /* @__PURE__ */ new Map();
|
|
7885
|
+
let boundaryGeneration = 0;
|
|
7886
|
+
function machineDekRegistryGeneration() {
|
|
7887
|
+
return boundaryGeneration;
|
|
7888
|
+
}
|
|
7889
|
+
function discardMachineDek(machineId) {
|
|
7890
|
+
machineDekById.get(machineId)?.dek.fill(0);
|
|
7891
|
+
machineDekById.delete(machineId);
|
|
7892
|
+
}
|
|
7893
|
+
function machineFingerprint(envelope) {
|
|
7894
|
+
return `${envelope}|${companyKeyChainVersion()}`;
|
|
7895
|
+
}
|
|
7896
|
+
async function resolveMachineDataKeyFields(record2, legacyKeys, privateKeys, scope) {
|
|
7897
|
+
const discard = { machineId: record2.id, fields: null, entry: null };
|
|
7898
|
+
if (!record2.dataEncryptionKey || !record2.metadata) return discard;
|
|
7899
|
+
if (decryptJsonWithCandidates(record2.metadata, legacyKeys)) return discard;
|
|
7900
|
+
const fingerprint = machineFingerprint(record2.dataEncryptionKey);
|
|
7901
|
+
const cached = machineDekById.get(record2.id);
|
|
7902
|
+
const machineKey = cached?.fingerprint === fingerprint ? cached.dek : unwrapMachineKey(record2.dataEncryptionKey, privateKeys);
|
|
7903
|
+
if (!machineKey) return discard;
|
|
7904
|
+
const metadata = await decryptDataKeyRecord(machineKey, record2.metadata);
|
|
7905
|
+
if (!metadata) return discard;
|
|
7906
|
+
const daemonState = record2.daemonState ? await decryptDataKeyRecord(machineKey, record2.daemonState) : null;
|
|
7907
|
+
const entry = cached?.dek === machineKey ? cached : { dek: machineKey, scope, fingerprint };
|
|
7908
|
+
entry.scope = scope;
|
|
7909
|
+
return { machineId: record2.id, fields: { metadata, daemonState }, entry };
|
|
7910
|
+
}
|
|
7911
|
+
function commitMachineDek(resolved) {
|
|
7912
|
+
if (resolved.entry) machineDekById.set(resolved.machineId, resolved.entry);
|
|
7913
|
+
else discardMachineDek(resolved.machineId);
|
|
7914
|
+
}
|
|
7915
|
+
function unwrapMachineKey(envelope, privateKeys) {
|
|
7916
|
+
for (const privateKey of privateKeys) {
|
|
7917
|
+
const machineKey = unwrapDataEncryptionKey(envelope, privateKey);
|
|
7918
|
+
if (machineKey) return machineKey;
|
|
7919
|
+
}
|
|
7920
|
+
return null;
|
|
7921
|
+
}
|
|
7922
|
+
async function decryptMachineDataKeyRows(rows, legacyKeys, options, scope, sinceGeneration = machineDekRegistryGeneration()) {
|
|
7923
|
+
const none = () => rows.map(() => null);
|
|
7924
|
+
if (sinceGeneration !== boundaryGeneration) return none();
|
|
7925
|
+
const listed = new Set(rows.map((row2) => row2.id));
|
|
7926
|
+
for (const [machineId, entry] of [...machineDekById]) {
|
|
7927
|
+
if (entry.scope === scope && !listed.has(machineId)) discardMachineDek(machineId);
|
|
7928
|
+
}
|
|
7929
|
+
if (!options.accountPrivateKey || !rows.some((row2) => row2.dataEncryptionKey)) return none();
|
|
7930
|
+
await options.dataKeyReady;
|
|
7931
|
+
if (sinceGeneration !== boundaryGeneration) return none();
|
|
7932
|
+
const privateKeys = dataKeyUnwrapCandidates(options.accountPrivateKey);
|
|
7933
|
+
const resolved = await Promise.all(rows.map((row2) => resolveMachineDataKeyFields(row2, legacyKeys, privateKeys, scope)));
|
|
7934
|
+
if (sinceGeneration !== boundaryGeneration) return none();
|
|
7935
|
+
for (const entry of resolved) commitMachineDek(entry);
|
|
7936
|
+
return resolved.map((entry) => entry.fields);
|
|
7937
|
+
}
|
|
7123
7938
|
function buildUserTextMessageContent(input) {
|
|
7124
7939
|
return {
|
|
7125
7940
|
role: "user",
|
|
@@ -7238,69 +8053,69 @@ function personalMachinesPath(companyId) {
|
|
|
7238
8053
|
function myMachinesPath(companyId) {
|
|
7239
8054
|
return `/api/admin/companies/${encodeURIComponent(companyId)}/my-machines`;
|
|
7240
8055
|
}
|
|
7241
|
-
function record$
|
|
8056
|
+
function record$4(value2) {
|
|
7242
8057
|
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
7243
8058
|
}
|
|
7244
8059
|
function array(value2) {
|
|
7245
8060
|
return Array.isArray(value2) ? value2 : [];
|
|
7246
8061
|
}
|
|
7247
|
-
function str(value2, fallback = "") {
|
|
8062
|
+
function str$1(value2, fallback = "") {
|
|
7248
8063
|
return typeof value2 === "string" ? value2 : fallback;
|
|
7249
8064
|
}
|
|
7250
|
-
function num(value2, fallback = 0) {
|
|
8065
|
+
function num$1(value2, fallback = 0) {
|
|
7251
8066
|
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : fallback;
|
|
7252
8067
|
}
|
|
7253
8068
|
function nullableNum(value2) {
|
|
7254
8069
|
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : null;
|
|
7255
8070
|
}
|
|
7256
8071
|
function provider(value2) {
|
|
7257
|
-
return PROVIDERS.includes(str(value2)) ? str(value2) : null;
|
|
8072
|
+
return PROVIDERS.includes(str$1(value2)) ? str$1(value2) : null;
|
|
7258
8073
|
}
|
|
7259
8074
|
function parseBundle(value2) {
|
|
7260
|
-
const row2 = record$
|
|
7261
|
-
const id = str(row2.id);
|
|
8075
|
+
const row2 = record$4(value2);
|
|
8076
|
+
const id = str$1(row2.id);
|
|
7262
8077
|
const bundleProvider = provider(row2.provider);
|
|
7263
8078
|
if (!id || !bundleProvider) return null;
|
|
7264
8079
|
return {
|
|
7265
8080
|
id,
|
|
7266
|
-
companyId: str(row2.companyId),
|
|
8081
|
+
companyId: str$1(row2.companyId),
|
|
7267
8082
|
provider: bundleProvider,
|
|
7268
|
-
label: str(row2.label, id),
|
|
7269
|
-
formatVersion: num(row2.formatVersion, 1),
|
|
7270
|
-
accountCount: num(row2.accountCount),
|
|
7271
|
-
contentHash: str(row2.contentHash),
|
|
7272
|
-
version: num(row2.version, 1),
|
|
7273
|
-
createdById: str(row2.createdById),
|
|
7274
|
-
createdAt: num(row2.createdAt),
|
|
7275
|
-
updatedAt: num(row2.updatedAt)
|
|
8083
|
+
label: str$1(row2.label, id),
|
|
8084
|
+
formatVersion: num$1(row2.formatVersion, 1),
|
|
8085
|
+
accountCount: num$1(row2.accountCount),
|
|
8086
|
+
contentHash: str$1(row2.contentHash),
|
|
8087
|
+
version: num$1(row2.version, 1),
|
|
8088
|
+
createdById: str$1(row2.createdById),
|
|
8089
|
+
createdAt: num$1(row2.createdAt),
|
|
8090
|
+
updatedAt: num$1(row2.updatedAt)
|
|
7276
8091
|
};
|
|
7277
8092
|
}
|
|
7278
8093
|
function parseDeployment(value2) {
|
|
7279
|
-
const row2 = record$
|
|
7280
|
-
const machineId = str(row2.machineId);
|
|
7281
|
-
const bundleId = str(row2.bundleId);
|
|
8094
|
+
const row2 = record$4(value2);
|
|
8095
|
+
const machineId = str$1(row2.machineId);
|
|
8096
|
+
const bundleId = str$1(row2.bundleId);
|
|
7282
8097
|
if (!machineId || !bundleId) return null;
|
|
7283
|
-
const status = str(row2.status);
|
|
8098
|
+
const status = str$1(row2.status);
|
|
7284
8099
|
return {
|
|
7285
|
-
id: str(row2.id),
|
|
7286
|
-
companyId: str(row2.companyId),
|
|
8100
|
+
id: str$1(row2.id),
|
|
8101
|
+
companyId: str$1(row2.companyId),
|
|
7287
8102
|
bundleId,
|
|
7288
8103
|
machineId,
|
|
7289
8104
|
bundleVersion: nullableNum(row2.bundleVersion),
|
|
7290
|
-
attemptedBundleVersion: num(row2.attemptedBundleVersion, num(row2.bundleVersion)),
|
|
8105
|
+
attemptedBundleVersion: num$1(row2.attemptedBundleVersion, num$1(row2.bundleVersion)),
|
|
7291
8106
|
status: status === "deployed" || status === "failed" || status === "pending" ? status : "pending",
|
|
7292
8107
|
errorKind: typeof row2.errorKind === "string" ? row2.errorKind : null,
|
|
7293
8108
|
deployedAt: nullableNum(row2.deployedAt),
|
|
7294
|
-
createdAt: num(row2.createdAt),
|
|
7295
|
-
updatedAt: num(row2.updatedAt)
|
|
8109
|
+
createdAt: num$1(row2.createdAt),
|
|
8110
|
+
updatedAt: num$1(row2.updatedAt)
|
|
7296
8111
|
};
|
|
7297
8112
|
}
|
|
7298
8113
|
function parseStatus(value2) {
|
|
7299
|
-
const row2 = record$
|
|
8114
|
+
const row2 = record$4(value2);
|
|
7300
8115
|
const statusProvider = provider(row2.provider);
|
|
7301
8116
|
if (!statusProvider) return void 0;
|
|
7302
|
-
const rotationRow = record$
|
|
7303
|
-
const state = str(rotationRow.state);
|
|
8117
|
+
const rotationRow = record$4(row2.rotation);
|
|
8118
|
+
const state = str$1(rotationRow.state);
|
|
7304
8119
|
const rotation = ROTATION_STATES.includes(state) ? {
|
|
7305
8120
|
state,
|
|
7306
8121
|
lastErrorKind: typeof rotationRow.lastErrorKind === "string" ? rotationRow.lastErrorKind : null,
|
|
@@ -7320,11 +8135,11 @@ function parseStatus(value2) {
|
|
|
7320
8135
|
};
|
|
7321
8136
|
}
|
|
7322
8137
|
function parseMachineResult(value2) {
|
|
7323
|
-
const row2 = record$
|
|
7324
|
-
const machineId = str(row2.machineId);
|
|
8138
|
+
const row2 = record$4(value2);
|
|
8139
|
+
const machineId = str$1(row2.machineId);
|
|
7325
8140
|
if (!machineId) return null;
|
|
7326
8141
|
if (row2.ok !== true) {
|
|
7327
|
-
return { machineId, ok: false, errorKind: str(row2.errorKind, "UNKNOWN_ERROR") };
|
|
8142
|
+
return { machineId, ok: false, errorKind: str$1(row2.errorKind, "UNKNOWN_ERROR") };
|
|
7328
8143
|
}
|
|
7329
8144
|
const status = parseStatus(row2.status);
|
|
7330
8145
|
return {
|
|
@@ -7335,54 +8150,54 @@ function parseMachineResult(value2) {
|
|
|
7335
8150
|
};
|
|
7336
8151
|
}
|
|
7337
8152
|
function parseAiCredentialVaultSnapshot(value2) {
|
|
7338
|
-
const body = record$
|
|
8153
|
+
const body = record$4(value2);
|
|
7339
8154
|
return {
|
|
7340
8155
|
bundles: array(body.bundles).map(parseBundle).filter((bundle) => bundle !== null),
|
|
7341
8156
|
deployments: array(body.deployments).map(parseDeployment).filter((deployment) => deployment !== null)
|
|
7342
8157
|
};
|
|
7343
8158
|
}
|
|
7344
8159
|
function parseAiCredentialCaptureResponse(value2) {
|
|
7345
|
-
const body = record$
|
|
8160
|
+
const body = record$4(value2);
|
|
7346
8161
|
const bundle = parseBundle(body.bundle);
|
|
7347
8162
|
if (!bundle) throw new Error("AI credential capture response did not contain a bundle");
|
|
7348
8163
|
return { bundle, warnings: array(body.warnings).filter((warning) => typeof warning === "string") };
|
|
7349
8164
|
}
|
|
7350
8165
|
function parseAiCredentialBatchResult(value2) {
|
|
7351
|
-
const body = record$
|
|
8166
|
+
const body = record$4(value2);
|
|
7352
8167
|
return {
|
|
7353
8168
|
results: array(body.results).map(parseMachineResult).filter((result) => result !== null),
|
|
7354
8169
|
warnings: array(body.warnings).filter((warning) => typeof warning === "string")
|
|
7355
8170
|
};
|
|
7356
8171
|
}
|
|
7357
8172
|
function parseSelfServiceMachines(value2) {
|
|
7358
|
-
return array(record$
|
|
7359
|
-
const row2 = record$
|
|
8173
|
+
return array(record$4(value2).machines).map((entry) => {
|
|
8174
|
+
const row2 = record$4(entry);
|
|
7360
8175
|
return {
|
|
7361
|
-
machineId: str(row2.machineId),
|
|
8176
|
+
machineId: str$1(row2.machineId),
|
|
7362
8177
|
type: row2.type === "shared" ? "shared" : "personal",
|
|
7363
8178
|
displayName: typeof row2.displayName === "string" ? row2.displayName : null,
|
|
7364
8179
|
host: typeof row2.host === "string" ? row2.host : null,
|
|
7365
8180
|
workspaceRoot: typeof row2.workspaceRoot === "string" ? row2.workspaceRoot : null,
|
|
7366
|
-
registeredAt: num(row2.registeredAt),
|
|
8181
|
+
registeredAt: num$1(row2.registeredAt),
|
|
7367
8182
|
online: typeof row2.online === "boolean" ? row2.online : null
|
|
7368
8183
|
};
|
|
7369
8184
|
}).filter((machine) => machine.machineId !== "");
|
|
7370
8185
|
}
|
|
7371
8186
|
function parsePersonalMachines(value2) {
|
|
7372
|
-
return array(record$
|
|
7373
|
-
const row2 = record$
|
|
8187
|
+
return array(record$4(value2).machines).map((entry) => {
|
|
8188
|
+
const row2 = record$4(entry);
|
|
7374
8189
|
const type = row2.type === "shared" ? "shared" : "personal";
|
|
7375
8190
|
return {
|
|
7376
|
-
machineId: str(row2.machineId),
|
|
7377
|
-
ownerId: str(row2.ownerId),
|
|
8191
|
+
machineId: str$1(row2.machineId),
|
|
8192
|
+
ownerId: str$1(row2.ownerId),
|
|
7378
8193
|
ownerUsername: typeof row2.ownerUsername === "string" ? row2.ownerUsername : null,
|
|
7379
8194
|
ownerDisplayName: typeof row2.ownerDisplayName === "string" ? row2.ownerDisplayName : null,
|
|
7380
8195
|
type,
|
|
7381
8196
|
accessList: array(row2.accessList).filter((id) => typeof id === "string"),
|
|
7382
8197
|
displayName: typeof row2.displayName === "string" ? row2.displayName : null,
|
|
7383
8198
|
workspaceRoot: typeof row2.workspaceRoot === "string" ? row2.workspaceRoot : null,
|
|
7384
|
-
registeredAt: num(row2.registeredAt),
|
|
7385
|
-
updatedAt: num(row2.updatedAt),
|
|
8199
|
+
registeredAt: num$1(row2.registeredAt),
|
|
8200
|
+
updatedAt: num$1(row2.updatedAt),
|
|
7386
8201
|
online: typeof row2.online === "boolean" ? row2.online : null,
|
|
7387
8202
|
activeAt: nullableNum(row2.activeAt),
|
|
7388
8203
|
host: typeof row2.host === "string" ? row2.host : null,
|
|
@@ -7395,6 +8210,144 @@ function parsePersonalMachines(value2) {
|
|
|
7395
8210
|
};
|
|
7396
8211
|
}).filter((machine) => machine.machineId !== "");
|
|
7397
8212
|
}
|
|
8213
|
+
const orgSearchPaths = {
|
|
8214
|
+
/** Fetched by main only (secrets) — kept here so the path has one origin. */
|
|
8215
|
+
config: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-config`,
|
|
8216
|
+
reissue: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-config/reissue`,
|
|
8217
|
+
integration: (companyId) => `/api/admin/companies/${encodeURIComponent(companyId)}/search-integration`,
|
|
8218
|
+
acl: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-acl`,
|
|
8219
|
+
leases: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/leases`,
|
|
8220
|
+
cursors: (companyId, projectId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/cursors${projectId === void 0 ? "" : `?projectId=${encodeURIComponent(projectId)}`}`,
|
|
8221
|
+
progress: (companyId) => `/api/companies/${encodeURIComponent(companyId)}/search-index/progress`
|
|
8222
|
+
};
|
|
8223
|
+
function record$3(value2) {
|
|
8224
|
+
return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
8225
|
+
}
|
|
8226
|
+
function str(value2, fallback = "") {
|
|
8227
|
+
return typeof value2 === "string" ? value2 : fallback;
|
|
8228
|
+
}
|
|
8229
|
+
function num(value2, fallback = 0) {
|
|
8230
|
+
return typeof value2 === "number" && Number.isFinite(value2) ? value2 : fallback;
|
|
8231
|
+
}
|
|
8232
|
+
const PHASES = ["idle", "waiting", "indexing", "done"];
|
|
8233
|
+
function parseOrgSearchCursors(raw) {
|
|
8234
|
+
const list = Array.isArray(record$3(raw).cursors) ? record$3(raw).cursors : [];
|
|
8235
|
+
return list.flatMap((item) => {
|
|
8236
|
+
const c = record$3(item);
|
|
8237
|
+
if (typeof c.sessionId !== "string" || typeof c.lastSeq !== "number") return [];
|
|
8238
|
+
return [{
|
|
8239
|
+
sessionId: c.sessionId,
|
|
8240
|
+
lastSeq: c.lastSeq,
|
|
8241
|
+
complete: c.complete === true,
|
|
8242
|
+
failureKind: typeof c.failureKind === "string" ? c.failureKind : null,
|
|
8243
|
+
updatedAt: num(c.updatedAt)
|
|
8244
|
+
}];
|
|
8245
|
+
});
|
|
8246
|
+
}
|
|
8247
|
+
function parseProjectProgress(raw) {
|
|
8248
|
+
const p = record$3(raw);
|
|
8249
|
+
return {
|
|
8250
|
+
projectId: str(p.projectId),
|
|
8251
|
+
sessionsTotal: num(p.sessionsTotal),
|
|
8252
|
+
sessionsIndexed: num(p.sessionsIndexed),
|
|
8253
|
+
sessionsFailed: num(p.sessionsFailed),
|
|
8254
|
+
messagesIndexed: num(p.messagesIndexed),
|
|
8255
|
+
phase: PHASES.includes(p.phase) ? p.phase : "idle",
|
|
8256
|
+
leaseHolderMasked: typeof p.leaseHolderMasked === "string" ? p.leaseHolderMasked : null,
|
|
8257
|
+
updatedAt: typeof p.updatedAt === "number" ? p.updatedAt : null
|
|
8258
|
+
};
|
|
8259
|
+
}
|
|
8260
|
+
function parseOrgSearchProgress(raw) {
|
|
8261
|
+
const r = record$3(raw);
|
|
8262
|
+
return {
|
|
8263
|
+
phase: PHASES.includes(r.phase) ? r.phase : "idle",
|
|
8264
|
+
sessionsTotal: num(r.sessionsTotal),
|
|
8265
|
+
sessionsIndexed: num(r.sessionsIndexed),
|
|
8266
|
+
sessionsFailed: num(r.sessionsFailed),
|
|
8267
|
+
messagesIndexed: num(r.messagesIndexed),
|
|
8268
|
+
activeLeases: num(r.activeLeases),
|
|
8269
|
+
updatedAt: typeof r.updatedAt === "number" ? r.updatedAt : null,
|
|
8270
|
+
projects: (Array.isArray(r.projects) ? r.projects : []).map(parseProjectProgress)
|
|
8271
|
+
};
|
|
8272
|
+
}
|
|
8273
|
+
function isApiError(error) {
|
|
8274
|
+
return error instanceof Error && error.name === "ApiError" && typeof error.status === "number" && typeof error.body === "string";
|
|
8275
|
+
}
|
|
8276
|
+
function errorCode(error) {
|
|
8277
|
+
try {
|
|
8278
|
+
const parsed = JSON.parse(error.body);
|
|
8279
|
+
return typeof parsed.error === "string" ? parsed.error : null;
|
|
8280
|
+
} catch {
|
|
8281
|
+
return null;
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
function classify404(error) {
|
|
8285
|
+
const code = errorCode(error);
|
|
8286
|
+
if (code === "search-integration-not-configured") return { kind: "not-configured" };
|
|
8287
|
+
if (code === "project-not-found") return { kind: "not-found" };
|
|
8288
|
+
return { kind: "unsupported" };
|
|
8289
|
+
}
|
|
8290
|
+
async function attempt(run, onError) {
|
|
8291
|
+
try {
|
|
8292
|
+
return await run();
|
|
8293
|
+
} catch (error) {
|
|
8294
|
+
if (isApiError(error)) {
|
|
8295
|
+
const mapped = onError(error);
|
|
8296
|
+
if (mapped !== null) return mapped;
|
|
8297
|
+
}
|
|
8298
|
+
throw error;
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
function createOrgSearchApi(transport) {
|
|
8302
|
+
const json = (body, method) => ({ method, body: JSON.stringify(body) });
|
|
8303
|
+
return {
|
|
8304
|
+
/** Member whose key returned 401 asks the reconciler for a new one (state → reissue). */
|
|
8305
|
+
requestKeyReissue: (companyId) => attempt(
|
|
8306
|
+
async () => {
|
|
8307
|
+
await transport.request(orgSearchPaths.reissue(companyId), { method: "POST" });
|
|
8308
|
+
return { kind: "ok" };
|
|
8309
|
+
},
|
|
8310
|
+
(error) => error.status === 404 ? errorCode(error) === "member-key-not-found" ? { kind: "not-found" } : { kind: "unsupported" } : null
|
|
8311
|
+
),
|
|
8312
|
+
getIntegration: (companyId) => attempt(
|
|
8313
|
+
async () => ({ kind: "ok", integration: await transport.request(orgSearchPaths.integration(companyId)) }),
|
|
8314
|
+
(error) => error.status === 404 ? classify404(error) : null
|
|
8315
|
+
),
|
|
8316
|
+
deleteIntegration: async (companyId) => {
|
|
8317
|
+
await transport.request(orgSearchPaths.integration(companyId), { method: "DELETE" });
|
|
8318
|
+
},
|
|
8319
|
+
getAcl: (companyId) => transport.request(orgSearchPaths.acl(companyId)),
|
|
8320
|
+
putAcl: (companyId, input) => attempt(
|
|
8321
|
+
async () => {
|
|
8322
|
+
await transport.request(orgSearchPaths.acl(companyId), json(input, "PUT"));
|
|
8323
|
+
return { kind: "ok" };
|
|
8324
|
+
},
|
|
8325
|
+
(error) => error.status === 409 ? { kind: "lease-not-held" } : error.status === 404 ? { kind: "unsupported" } : null
|
|
8326
|
+
),
|
|
8327
|
+
lease: (companyId, projectId, action) => attempt(
|
|
8328
|
+
async () => {
|
|
8329
|
+
const lease = await transport.request(orgSearchPaths.leases(companyId), json({ projectId, action }, "POST"));
|
|
8330
|
+
return action === "release" ? { kind: "released" } : { kind: "ok", lease };
|
|
8331
|
+
},
|
|
8332
|
+
(error) => error.status === 404 ? classify404(error) : error.status === 403 ? { kind: "forbidden" } : null
|
|
8333
|
+
),
|
|
8334
|
+
getCursors: (companyId, projectId) => attempt(
|
|
8335
|
+
async () => ({ kind: "ok", cursors: parseOrgSearchCursors(await transport.request(orgSearchPaths.cursors(companyId, projectId))) }),
|
|
8336
|
+
(error) => error.status === 404 ? classify404(error) : null
|
|
8337
|
+
),
|
|
8338
|
+
putCursors: (companyId, projectId, cursors) => attempt(
|
|
8339
|
+
async () => {
|
|
8340
|
+
await transport.request(orgSearchPaths.cursors(companyId), json({ projectId, cursors }, "PUT"));
|
|
8341
|
+
return { kind: "ok" };
|
|
8342
|
+
},
|
|
8343
|
+
(error) => error.status === 409 ? { kind: "lease-not-held" } : error.status === 404 ? classify404(error) : null
|
|
8344
|
+
),
|
|
8345
|
+
getProgress: (companyId) => attempt(
|
|
8346
|
+
async () => ({ kind: "ok", progress: parseOrgSearchProgress(await transport.request(orgSearchPaths.progress(companyId))) }),
|
|
8347
|
+
(error) => error.status === 404 ? { kind: "unsupported" } : null
|
|
8348
|
+
)
|
|
8349
|
+
};
|
|
8350
|
+
}
|
|
7398
8351
|
const SERVER_MACHINE_CONCURRENCY = 4;
|
|
7399
8352
|
const MAX_FANOUT_TIMEOUT_MS = 9e5;
|
|
7400
8353
|
function machineFanoutTimeoutMs(perMachineMs, machineCount) {
|
|
@@ -7764,7 +8717,7 @@ const PUBLICATION_KEYS = /* @__PURE__ */ new Set([
|
|
|
7764
8717
|
function fail(detail) {
|
|
7765
8718
|
throw new Error(`artifact publication response is invalid: ${detail}`);
|
|
7766
8719
|
}
|
|
7767
|
-
function record$
|
|
8720
|
+
function record$2(value2, label) {
|
|
7768
8721
|
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) fail(`${label} must be an object`);
|
|
7769
8722
|
return value2;
|
|
7770
8723
|
}
|
|
@@ -7810,7 +8763,7 @@ function sourceIdentity(source) {
|
|
|
7810
8763
|
]) : JSON.stringify([source.chatId, source.path]);
|
|
7811
8764
|
}
|
|
7812
8765
|
function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
|
|
7813
|
-
const publication = record$
|
|
8766
|
+
const publication = record$2(value2, "publication");
|
|
7814
8767
|
if (Object.keys(publication).some((key) => !PUBLICATION_KEYS.has(key))) {
|
|
7815
8768
|
fail("publication contains an unexpected field");
|
|
7816
8769
|
}
|
|
@@ -7843,7 +8796,7 @@ function validatePublication(value2, apiBaseUrl, operation, expectedSource) {
|
|
|
7843
8796
|
};
|
|
7844
8797
|
}
|
|
7845
8798
|
function parseStaticArtifactPublicationResult(value2, options) {
|
|
7846
|
-
const response = record$
|
|
8799
|
+
const response = record$2(value2, "response");
|
|
7847
8800
|
if (options.operation === "publish") {
|
|
7848
8801
|
exactKeys(response, ["publication", "publicUrl", "reused"]);
|
|
7849
8802
|
if (typeof response.reused !== "boolean") fail("reused is invalid");
|
|
@@ -11400,14 +12353,14 @@ function requireReceiver() {
|
|
|
11400
12353
|
* @return {(Error|RangeError)} The error
|
|
11401
12354
|
* @private
|
|
11402
12355
|
*/
|
|
11403
|
-
createError(ErrorCtor, message, prefix, statusCode,
|
|
12356
|
+
createError(ErrorCtor, message, prefix, statusCode, errorCode2) {
|
|
11404
12357
|
this._loop = false;
|
|
11405
12358
|
this._errored = true;
|
|
11406
12359
|
const err = new ErrorCtor(
|
|
11407
12360
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
|
11408
12361
|
);
|
|
11409
12362
|
Error.captureStackTrace(err, this.createError);
|
|
11410
|
-
err.code =
|
|
12363
|
+
err.code = errorCode2;
|
|
11411
12364
|
err[kStatusCode] = statusCode;
|
|
11412
12365
|
return err;
|
|
11413
12366
|
}
|
|
@@ -16178,10 +17131,10 @@ class Manager extends Emitter {
|
|
|
16178
17131
|
* @private
|
|
16179
17132
|
*/
|
|
16180
17133
|
onreconnect() {
|
|
16181
|
-
const
|
|
17134
|
+
const attempt2 = this.backoff.attempts;
|
|
16182
17135
|
this._reconnecting = false;
|
|
16183
17136
|
this.backoff.reset();
|
|
16184
|
-
this.emitReserved("reconnect",
|
|
17137
|
+
this.emitReserved("reconnect", attempt2);
|
|
16185
17138
|
}
|
|
16186
17139
|
}
|
|
16187
17140
|
const debug = debugModule("socket.io-client");
|
|
@@ -16580,6 +17533,16 @@ function buildWorktreeMutationBody(input, options = {}) {
|
|
|
16580
17533
|
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)))
|
|
16581
17534
|
));
|
|
16582
17535
|
}
|
|
17536
|
+
function readDaemonRunning(decoded) {
|
|
17537
|
+
if (!decoded || typeof decoded !== "object") return null;
|
|
17538
|
+
const { status, state } = decoded;
|
|
17539
|
+
return knownDaemonState(status) ?? knownDaemonState(state);
|
|
17540
|
+
}
|
|
17541
|
+
function knownDaemonState(value2) {
|
|
17542
|
+
if (value2 === "running") return true;
|
|
17543
|
+
if (value2 === "stopped" || value2 === "shutting-down") return false;
|
|
17544
|
+
return null;
|
|
17545
|
+
}
|
|
16583
17546
|
const STANDALONE_USER_ID = "standalone-local";
|
|
16584
17547
|
function isStandaloneAuthSession(session) {
|
|
16585
17548
|
return session?.userId === STANDALONE_USER_ID;
|
|
@@ -16663,19 +17626,19 @@ function row(value2) {
|
|
|
16663
17626
|
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) throw new Error("automation-response-invalid");
|
|
16664
17627
|
return value2;
|
|
16665
17628
|
}
|
|
16666
|
-
function stringValue(value2) {
|
|
17629
|
+
function stringValue$1(value2) {
|
|
16667
17630
|
if (typeof value2 !== "string" || !value2) throw new Error("automation-response-invalid");
|
|
16668
17631
|
return value2;
|
|
16669
17632
|
}
|
|
16670
17633
|
function nullableString(value2) {
|
|
16671
|
-
return value2 === null ? null : stringValue(value2);
|
|
17634
|
+
return value2 === null ? null : stringValue$1(value2);
|
|
16672
17635
|
}
|
|
16673
|
-
function integer(value2, min = 0) {
|
|
17636
|
+
function integer$1(value2, min = 0) {
|
|
16674
17637
|
if (!Number.isSafeInteger(value2) || value2 < min) throw new Error("automation-response-invalid");
|
|
16675
17638
|
return value2;
|
|
16676
17639
|
}
|
|
16677
17640
|
function nullableInteger(value2) {
|
|
16678
|
-
return value2 === null ? null : integer(value2);
|
|
17641
|
+
return value2 === null ? null : integer$1(value2);
|
|
16679
17642
|
}
|
|
16680
17643
|
function optionalNullableInteger(value2) {
|
|
16681
17644
|
return value2 === void 0 ? null : nullableInteger(value2);
|
|
@@ -16683,40 +17646,40 @@ function optionalNullableInteger(value2) {
|
|
|
16683
17646
|
function parseRuntimeState(value2) {
|
|
16684
17647
|
const item = row(value2);
|
|
16685
17648
|
return {
|
|
16686
|
-
automationId: stringValue(item.automationId),
|
|
16687
|
-
generation: integer(item.generation, 1),
|
|
16688
|
-
stateVersion: integer(item.stateVersion, 1),
|
|
17649
|
+
automationId: stringValue$1(item.automationId),
|
|
17650
|
+
generation: integer$1(item.generation, 1),
|
|
17651
|
+
stateVersion: integer$1(item.stateVersion, 1),
|
|
16689
17652
|
stateCiphertext: versionedAutomationBase64(item.stateCiphertext, 96 * 1024),
|
|
16690
|
-
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
17653
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion, 1),
|
|
16691
17654
|
viewerKeyEnvelope: versionedAutomationBase64(item.viewerKeyEnvelope, 105, 105),
|
|
16692
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
16693
|
-
updatedAt: integer(item.updatedAt)
|
|
17655
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
17656
|
+
updatedAt: integer$1(item.updatedAt)
|
|
16694
17657
|
};
|
|
16695
17658
|
}
|
|
16696
17659
|
function parseServerAutomationPublic(value2) {
|
|
16697
17660
|
const item = row(value2);
|
|
16698
17661
|
if (item.payloadVersion !== 1 && item.payloadVersion !== 2 || typeof item.paused !== "boolean") throw new Error("automation-response-invalid");
|
|
16699
17662
|
const parsed = {
|
|
16700
|
-
id: stringValue(item.id),
|
|
16701
|
-
projectId: stringValue(item.projectId),
|
|
16702
|
-
ownerAccountId: stringValue(item.ownerAccountId),
|
|
17663
|
+
id: stringValue$1(item.id),
|
|
17664
|
+
projectId: stringValue$1(item.projectId),
|
|
17665
|
+
ownerAccountId: stringValue$1(item.ownerAccountId),
|
|
16703
17666
|
machineAccountId: nullableString(item.machineAccountId),
|
|
16704
17667
|
machineId: nullableString(item.machineId),
|
|
16705
|
-
revision: integer(item.revision, 1),
|
|
16706
|
-
generation: integer(item.generation, 1),
|
|
17668
|
+
revision: integer$1(item.revision, 1),
|
|
17669
|
+
generation: integer$1(item.generation, 1),
|
|
16707
17670
|
payloadVersion: item.payloadVersion,
|
|
16708
|
-
payloadCiphertext: stringValue(item.payloadCiphertext),
|
|
16709
|
-
viewerKeyId: stringValue(item.viewerKeyId),
|
|
16710
|
-
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
16711
|
-
viewerKeyEnvelope: stringValue(item.viewerKeyEnvelope),
|
|
16712
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17671
|
+
payloadCiphertext: stringValue$1(item.payloadCiphertext),
|
|
17672
|
+
viewerKeyId: stringValue$1(item.viewerKeyId),
|
|
17673
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion, 1),
|
|
17674
|
+
viewerKeyEnvelope: stringValue$1(item.viewerKeyEnvelope),
|
|
17675
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
16713
17676
|
paused: item.paused,
|
|
16714
|
-
enabledAt: integer(item.enabledAt),
|
|
17677
|
+
enabledAt: integer$1(item.enabledAt),
|
|
16715
17678
|
runRequestedAt: optionalNullableInteger(item.runRequestedAt),
|
|
16716
|
-
appliedRevision: integer(item.appliedRevision),
|
|
17679
|
+
appliedRevision: integer$1(item.appliedRevision),
|
|
16717
17680
|
appliedAt: nullableInteger(item.appliedAt),
|
|
16718
|
-
createdAt: integer(item.createdAt),
|
|
16719
|
-
updatedAt: integer(item.updatedAt),
|
|
17681
|
+
createdAt: integer$1(item.createdAt),
|
|
17682
|
+
updatedAt: integer$1(item.updatedAt),
|
|
16720
17683
|
runtimeState: item.runtimeState === void 0 || item.runtimeState === null ? null : parseRuntimeState(item.runtimeState)
|
|
16721
17684
|
};
|
|
16722
17685
|
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)) {
|
|
@@ -16738,13 +17701,14 @@ function parseServerAutomationAdoption(value2) {
|
|
|
16738
17701
|
function parseServerAutomationTarget(value2) {
|
|
16739
17702
|
const item = row(value2);
|
|
16740
17703
|
return {
|
|
16741
|
-
machineAccountId: stringValue(item.machineAccountId),
|
|
16742
|
-
machineId: stringValue(item.machineId),
|
|
16743
|
-
machinePublicKey: stringValue(item.machinePublicKey),
|
|
16744
|
-
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17704
|
+
machineAccountId: stringValue$1(item.machineAccountId),
|
|
17705
|
+
machineId: stringValue$1(item.machineId),
|
|
17706
|
+
machinePublicKey: stringValue$1(item.machinePublicKey),
|
|
17707
|
+
machineKeyVersion: integer$1(item.machineKeyVersion, 1),
|
|
16745
17708
|
viewerPublicKey: nullableString(item.viewerPublicKey),
|
|
16746
|
-
viewerKeyVersion: integer(item.viewerKeyVersion),
|
|
16747
|
-
automationProtocolVersion: item.automationProtocolVersion === void 0 ? 1 : integer(item.automationProtocolVersion, 1)
|
|
17709
|
+
viewerKeyVersion: integer$1(item.viewerKeyVersion),
|
|
17710
|
+
automationProtocolVersion: item.automationProtocolVersion === void 0 ? 1 : integer$1(item.automationProtocolVersion, 1),
|
|
17711
|
+
sessionFollowupSupported: item.sessionFollowupSupported === true
|
|
16748
17712
|
};
|
|
16749
17713
|
}
|
|
16750
17714
|
const RUN_STATUSES = /* @__PURE__ */ new Set(["CLAIMED", "RUNNING", "COMPLETED", "FAILED", "CANCELLED", "EXPIRED", "ABANDONED"]);
|
|
@@ -16753,16 +17717,16 @@ function parseServerAutomationRun(value2) {
|
|
|
16753
17717
|
const item = row(value2);
|
|
16754
17718
|
if (!RUN_STATUSES.has(item.status) || item.outcome !== null && !RUN_OUTCOMES.has(item.outcome) || typeof item.lateReport !== "boolean") throw new Error("automation-response-invalid");
|
|
16755
17719
|
return {
|
|
16756
|
-
id: stringValue(item.id),
|
|
16757
|
-
automationId: stringValue(item.automationId),
|
|
16758
|
-
generation: integer(item.generation, 1),
|
|
16759
|
-
scheduledFor: integer(item.scheduledFor),
|
|
16760
|
-
machineId: stringValue(item.machineId),
|
|
17720
|
+
id: stringValue$1(item.id),
|
|
17721
|
+
automationId: stringValue$1(item.automationId),
|
|
17722
|
+
generation: integer$1(item.generation, 1),
|
|
17723
|
+
scheduledFor: integer$1(item.scheduledFor),
|
|
17724
|
+
machineId: stringValue$1(item.machineId),
|
|
16761
17725
|
status: item.status,
|
|
16762
17726
|
sessionId: nullableString(item.sessionId),
|
|
16763
17727
|
outcome: item.outcome,
|
|
16764
17728
|
detailCiphertext: nullableString(item.detailCiphertext),
|
|
16765
|
-
claimedAt: integer(item.claimedAt),
|
|
17729
|
+
claimedAt: integer$1(item.claimedAt),
|
|
16766
17730
|
startedAt: nullableInteger(item.startedAt),
|
|
16767
17731
|
completedAt: nullableInteger(item.completedAt),
|
|
16768
17732
|
lateReport: item.lateReport,
|
|
@@ -16800,6 +17764,98 @@ function parseServerGithubWebhookStatus(value2) {
|
|
|
16800
17764
|
lastDeliveryAt: nullableTimestamp(item.lastDeliveryAt)
|
|
16801
17765
|
};
|
|
16802
17766
|
}
|
|
17767
|
+
const SESSION_FOLLOWUP_WIRE_VERSION = 1;
|
|
17768
|
+
const STATUSES$1 = /* @__PURE__ */ new Set(["WAITING", "DELIVERY_PENDING", "PAUSED", "COMPLETED", "CANCELLED", "FAILED"]);
|
|
17769
|
+
const TERMINAL_CODES = /* @__PURE__ */ new Set([
|
|
17770
|
+
"CLEAN",
|
|
17771
|
+
"LOW_OR_NIT_ONLY",
|
|
17772
|
+
"UNSTRUCTURED",
|
|
17773
|
+
"ROUNDS_EXHAUSTED",
|
|
17774
|
+
"USER_INTERVENTION",
|
|
17775
|
+
"STOPPED",
|
|
17776
|
+
"SESSION_UNAVAILABLE",
|
|
17777
|
+
"TARGET_MISMATCH",
|
|
17778
|
+
"DECRYPT_FAILED",
|
|
17779
|
+
"PERMISSION_REVOKED"
|
|
17780
|
+
]);
|
|
17781
|
+
function invalid() {
|
|
17782
|
+
throw new Error("session-followup-response-invalid");
|
|
17783
|
+
}
|
|
17784
|
+
function record$1(value2) {
|
|
17785
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) invalid();
|
|
17786
|
+
return value2;
|
|
17787
|
+
}
|
|
17788
|
+
function stringValue(value2) {
|
|
17789
|
+
return typeof value2 === "string" && value2 ? value2 : invalid();
|
|
17790
|
+
}
|
|
17791
|
+
function boundedString(value2, max) {
|
|
17792
|
+
const parsed = stringValue(value2);
|
|
17793
|
+
return parsed.length <= max ? parsed : invalid();
|
|
17794
|
+
}
|
|
17795
|
+
function integer(value2, min = 0) {
|
|
17796
|
+
return Number.isSafeInteger(value2) && value2 >= min ? value2 : invalid();
|
|
17797
|
+
}
|
|
17798
|
+
function validateSessionFollowupCreateInput(input) {
|
|
17799
|
+
try {
|
|
17800
|
+
if (input.wireVersion !== SESSION_FOLLOWUP_WIRE_VERSION || input.payloadVersion !== 1) invalid();
|
|
17801
|
+
if (!input.sessionId || input.sessionId.length > 200 || !input.viewerKeyId || input.viewerKeyId.length > 128) invalid();
|
|
17802
|
+
const totalRounds = integer(input.totalRounds, 2);
|
|
17803
|
+
const currentRound = integer(input.currentRound, 1);
|
|
17804
|
+
if (totalRounds > 7 || currentRound > totalRounds) invalid();
|
|
17805
|
+
integer(input.responseBoundarySeq);
|
|
17806
|
+
integer(input.viewerKeyVersion, 1);
|
|
17807
|
+
integer(input.machineKeyVersion, 1);
|
|
17808
|
+
versionedAutomationBase64(input.payloadCiphertext, 128 * 1024);
|
|
17809
|
+
versionedAutomationBase64(input.viewerKeyEnvelope, 105, 105);
|
|
17810
|
+
versionedAutomationBase64(input.machineKeyEnvelope, 105, 105);
|
|
17811
|
+
return input;
|
|
17812
|
+
} catch {
|
|
17813
|
+
throw new Error("session-followup-request-invalid");
|
|
17814
|
+
}
|
|
17815
|
+
}
|
|
17816
|
+
function parseSessionFollowupPublic(value2) {
|
|
17817
|
+
try {
|
|
17818
|
+
const item = record$1(value2);
|
|
17819
|
+
if (!STATUSES$1.has(item.status)) invalid();
|
|
17820
|
+
if (item.terminalCode !== null && !TERMINAL_CODES.has(item.terminalCode)) invalid();
|
|
17821
|
+
if (item.payloadVersion !== 1) invalid();
|
|
17822
|
+
const totalRounds = integer(item.totalRounds, 2);
|
|
17823
|
+
const currentRound = integer(item.currentRound, 1);
|
|
17824
|
+
if (totalRounds > 7 || currentRound > totalRounds) invalid();
|
|
17825
|
+
return {
|
|
17826
|
+
id: stringValue(item.id),
|
|
17827
|
+
projectId: stringValue(item.projectId),
|
|
17828
|
+
ownerAccountId: stringValue(item.ownerAccountId),
|
|
17829
|
+
machineAccountId: stringValue(item.machineAccountId),
|
|
17830
|
+
machineId: stringValue(item.machineId),
|
|
17831
|
+
sessionId: stringValue(item.sessionId),
|
|
17832
|
+
revision: integer(item.revision, 1),
|
|
17833
|
+
generation: integer(item.generation, 1),
|
|
17834
|
+
step: integer(item.step, 1),
|
|
17835
|
+
status: item.status,
|
|
17836
|
+
terminalCode: item.terminalCode,
|
|
17837
|
+
totalRounds,
|
|
17838
|
+
currentRound,
|
|
17839
|
+
responseBoundarySeq: integer(item.responseBoundarySeq),
|
|
17840
|
+
lastObservedSeq: integer(item.lastObservedSeq),
|
|
17841
|
+
payloadVersion: 1,
|
|
17842
|
+
payloadCiphertext: versionedAutomationBase64(item.payloadCiphertext, 128 * 1024),
|
|
17843
|
+
viewerKeyId: boundedString(item.viewerKeyId, 128),
|
|
17844
|
+
viewerKeyVersion: integer(item.viewerKeyVersion, 1),
|
|
17845
|
+
viewerKeyEnvelope: versionedAutomationBase64(item.viewerKeyEnvelope, 105, 105),
|
|
17846
|
+
machineKeyVersion: integer(item.machineKeyVersion, 1),
|
|
17847
|
+
completedAt: item.completedAt === null ? null : integer(item.completedAt),
|
|
17848
|
+
createdAt: integer(item.createdAt),
|
|
17849
|
+
updatedAt: integer(item.updatedAt)
|
|
17850
|
+
};
|
|
17851
|
+
} catch {
|
|
17852
|
+
throw new Error("session-followup-response-invalid");
|
|
17853
|
+
}
|
|
17854
|
+
}
|
|
17855
|
+
function parseSessionFollowupList(value2) {
|
|
17856
|
+
if (!Array.isArray(value2)) throw new Error("session-followup-response-invalid");
|
|
17857
|
+
return value2.map(parseSessionFollowupPublic);
|
|
17858
|
+
}
|
|
16803
17859
|
const ERROR_CODES = /* @__PURE__ */ new Set([
|
|
16804
17860
|
"PAIRING_INVALID",
|
|
16805
17861
|
"PAIRING_EXPIRED",
|
|
@@ -16919,6 +17975,20 @@ class MachineDeleteConflictError extends Error {
|
|
|
16919
17975
|
const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
16920
17976
|
const LONG_REQUEST_TIMEOUT_MS = 18e4;
|
|
16921
17977
|
const UPLOAD_REQUEST_TIMEOUT_MS = 12e4;
|
|
17978
|
+
const OWNER_SYNC_WARNING_MAX_COUNT = 16;
|
|
17979
|
+
const OWNER_SYNC_WARNING_MAX_LENGTH = 100;
|
|
17980
|
+
function parseOwnerSyncWarnings(value2) {
|
|
17981
|
+
if (!Array.isArray(value2)) return [];
|
|
17982
|
+
const warnings2 = [];
|
|
17983
|
+
for (const item of value2) {
|
|
17984
|
+
if (typeof item !== "string") continue;
|
|
17985
|
+
const warning = item.trim();
|
|
17986
|
+
if (!warning || warning.length > OWNER_SYNC_WARNING_MAX_LENGTH) continue;
|
|
17987
|
+
warnings2.push(warning);
|
|
17988
|
+
if (warnings2.length === OWNER_SYNC_WARNING_MAX_COUNT) break;
|
|
17989
|
+
}
|
|
17990
|
+
return warnings2;
|
|
17991
|
+
}
|
|
16922
17992
|
function isRequestTimeoutError(error) {
|
|
16923
17993
|
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
16924
17994
|
}
|
|
@@ -16928,7 +17998,9 @@ async function fetchWithTimeout(url2, init, timeoutMs) {
|
|
|
16928
17998
|
return await fetch(url2, requestAbort.signal ? { ...init, signal: requestAbort.signal } : init);
|
|
16929
17999
|
} catch (error) {
|
|
16930
18000
|
if (requestAbort.didTimeout() && isRequestTimeoutError(error)) {
|
|
16931
|
-
|
|
18001
|
+
const timeout = new Error(translateCurrent("api.serverTimeout", { seconds: Math.round(timeoutMs / 1e3) }));
|
|
18002
|
+
timeout.name = "ServerTimeoutError";
|
|
18003
|
+
throw timeout;
|
|
16932
18004
|
}
|
|
16933
18005
|
throw error;
|
|
16934
18006
|
}
|
|
@@ -17017,7 +18089,8 @@ function normalizeGithubPullRequest(raw) {
|
|
|
17017
18089
|
head: typeof head === "string" ? head : null,
|
|
17018
18090
|
base: typeof base === "string" ? base : null,
|
|
17019
18091
|
draft: raw.draft === true,
|
|
17020
|
-
merged
|
|
18092
|
+
merged,
|
|
18093
|
+
...raw.autoMerge === true ? { autoMerge: true } : {}
|
|
17021
18094
|
};
|
|
17022
18095
|
}
|
|
17023
18096
|
function extractPullRequestList(raw) {
|
|
@@ -17045,14 +18118,15 @@ function normalizeAutomationSupport(value2) {
|
|
|
17045
18118
|
...Number.isSafeInteger(capability.keyVersion) && capability.keyVersion > 0 ? { keyVersion: capability.keyVersion } : {}
|
|
17046
18119
|
};
|
|
17047
18120
|
}
|
|
17048
|
-
function normalizeMachine(row2, candidateKeys) {
|
|
18121
|
+
function normalizeMachine(row2, candidateKeys, dataKeyFields = null) {
|
|
17049
18122
|
const registry = row2.registry;
|
|
17050
|
-
const decrypted = decryptJsonWithCandidates(row2.metadata, candidateKeys) ?? {};
|
|
17051
|
-
const daemonState = decryptJsonWithCandidates(row2.daemonState, candidateKeys);
|
|
18123
|
+
const decrypted = dataKeyFields?.metadata ?? decryptJsonWithCandidates(row2.metadata, candidateKeys) ?? {};
|
|
18124
|
+
const daemonState = dataKeyFields?.daemonState ?? decryptJsonWithCandidates(row2.daemonState, candidateKeys);
|
|
17052
18125
|
const resources = row2.resources ?? parseMachineResources(daemonState);
|
|
17053
18126
|
const host = row2.host || registry?.host || (typeof decrypted.host === "string" ? decrypted.host : row2.id.slice(0, 8));
|
|
17054
18127
|
const platform2 = row2.platform || registry?.platform || (typeof decrypted.platform === "string" ? decrypted.platform : "unknown");
|
|
17055
18128
|
const automationSupport = normalizeAutomationSupport(decrypted.automationSupport) ?? normalizeAutomationSupport(registry?.automationSupport);
|
|
18129
|
+
const autonomousQualityGate = decrypted.autonomousQualityGateSupport ?? registry?.autonomousQualityGateSupport;
|
|
17056
18130
|
return {
|
|
17057
18131
|
id: row2.id,
|
|
17058
18132
|
accountId: row2.accountId,
|
|
@@ -17065,8 +18139,10 @@ function normalizeMachine(row2, candidateKeys) {
|
|
|
17065
18139
|
happyCliVersion: row2.happyCliVersion || registry?.happyCliVersion || (typeof decrypted.happyCliVersion === "string" ? decrypted.happyCliVersion : void 0),
|
|
17066
18140
|
homeDir: row2.homeDir || registry?.homeDir || (typeof decrypted.homeDir === "string" ? decrypted.homeDir : void 0),
|
|
17067
18141
|
resources,
|
|
18142
|
+
daemonRunning: readDaemonRunning(daemonState),
|
|
17068
18143
|
mcpCallerGrantPublicKey: readMcpCallerGrantPublicKey(daemonState),
|
|
17069
18144
|
automationSupport,
|
|
18145
|
+
autonomousQualityGate,
|
|
17070
18146
|
filesystemScope: decrypted.filesystemScope,
|
|
17071
18147
|
additionalDirectories: decrypted.additionalDirectories,
|
|
17072
18148
|
registry
|
|
@@ -17096,8 +18172,57 @@ class ApiClient {
|
|
|
17096
18172
|
headers: { "Content-Type": "application/json" },
|
|
17097
18173
|
body: JSON.stringify(input)
|
|
17098
18174
|
}, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
18175
|
+
return parsePasswordLoginResponse(await this.handleResponse(response));
|
|
18176
|
+
}
|
|
18177
|
+
/**
|
|
18178
|
+
* MFA(TOTP) 로그인 2단계 — 서버 mfaLoginRoutes `/api/auth/mfa/login/totp`.
|
|
18179
|
+
* 성공 응답은 일반 로그인 결과의 상위집합이라 같은 strict parser 를 쓴다.
|
|
18180
|
+
* 실패는 handleResponse 의 ApiError(status + body 의 MFA_* code)로 온다.
|
|
18181
|
+
*/
|
|
18182
|
+
async verifyMfaTotpLogin(input) {
|
|
18183
|
+
const response = await fetchWithTimeout(absoluteUrl(this.config.apiBaseUrl, "/api/auth/mfa/login/totp"), {
|
|
18184
|
+
method: "POST",
|
|
18185
|
+
headers: { "Content-Type": "application/json" },
|
|
18186
|
+
body: JSON.stringify(input)
|
|
18187
|
+
}, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
17099
18188
|
return parseCloudLoginResult(await this.handleResponse(response));
|
|
17100
18189
|
}
|
|
18190
|
+
async startMfaPasskeyHandoff(input) {
|
|
18191
|
+
const response = await fetchWithTimeout(
|
|
18192
|
+
absoluteUrl(this.config.apiBaseUrl, "/api/auth/mfa/login/passkey-handoffs"),
|
|
18193
|
+
{
|
|
18194
|
+
method: "POST",
|
|
18195
|
+
headers: { "Content-Type": "application/json" },
|
|
18196
|
+
body: JSON.stringify({
|
|
18197
|
+
version: 1,
|
|
18198
|
+
loginToken: input.loginToken,
|
|
18199
|
+
clientKind: input.clientKind,
|
|
18200
|
+
...input.deviceId ? { deviceId: input.deviceId } : {},
|
|
18201
|
+
e2eeDeviceApprovalVersion: 1
|
|
18202
|
+
}),
|
|
18203
|
+
signal: input.signal
|
|
18204
|
+
},
|
|
18205
|
+
DEFAULT_REQUEST_TIMEOUT_MS
|
|
18206
|
+
);
|
|
18207
|
+
return parseMfaPasskeyHandoff(await this.handleResponse(response), this.config.apiBaseUrl);
|
|
18208
|
+
}
|
|
18209
|
+
async exchangeMfaPasskeyHandoff(input) {
|
|
18210
|
+
const response = await fetchWithTimeout(
|
|
18211
|
+
absoluteUrl(
|
|
18212
|
+
this.config.apiBaseUrl,
|
|
18213
|
+
`/api/auth/mfa/login/passkey-handoffs/${encodeURIComponent(input.handoffId)}/exchange`
|
|
18214
|
+
),
|
|
18215
|
+
{
|
|
18216
|
+
method: "POST",
|
|
18217
|
+
headers: { "Content-Type": "application/json" },
|
|
18218
|
+
body: JSON.stringify({ version: 1, exchangeToken: input.exchangeToken }),
|
|
18219
|
+
signal: input.signal
|
|
18220
|
+
},
|
|
18221
|
+
DEFAULT_REQUEST_TIMEOUT_MS
|
|
18222
|
+
);
|
|
18223
|
+
const data = await this.handleResponse(response);
|
|
18224
|
+
return response.status === 202 ? parsePendingMfaPasskeyExchange(data) : { kind: "complete", result: parseCloudLoginResult(data) };
|
|
18225
|
+
}
|
|
17101
18226
|
async getMe() {
|
|
17102
18227
|
return this.request("/api/auth/me", { boundary: "api" });
|
|
17103
18228
|
}
|
|
@@ -17394,6 +18519,17 @@ class ApiClient {
|
|
|
17394
18519
|
);
|
|
17395
18520
|
return Array.isArray(data) ? data : data.members ?? [];
|
|
17396
18521
|
}
|
|
18522
|
+
/**
|
|
18523
|
+
* 회사 공유 키 공개키 + 자기 grant 조회 (specs/e2ee-company-key-activation
|
|
18524
|
+
* P1). 응답 해석(myGrants null=미수립 vs []=권한없음)은 companyKeyChain 이
|
|
18525
|
+
* 담당하므로 여기서는 원본을 그대로 넘긴다.
|
|
18526
|
+
*/
|
|
18527
|
+
async fetchCompanyDataKey(companyId) {
|
|
18528
|
+
return this.request(
|
|
18529
|
+
`/api/admin/companies/${encodeURIComponent(companyId)}/data-key`,
|
|
18530
|
+
{ boundary: "api" }
|
|
18531
|
+
);
|
|
18532
|
+
}
|
|
17397
18533
|
async updateCompanyMemberRole(companyId, accountId, role) {
|
|
17398
18534
|
await this.request(`/api/admin/companies/${encodeURIComponent(companyId)}/members/${encodeURIComponent(accountId)}`, {
|
|
17399
18535
|
method: "PATCH",
|
|
@@ -17532,6 +18668,10 @@ class ApiClient {
|
|
|
17532
18668
|
}
|
|
17533
18669
|
);
|
|
17534
18670
|
}
|
|
18671
|
+
/** specs/org-elasticsearch-search — 경계 어댑터는 src/api/orgSearch.ts 가 소유한다. */
|
|
18672
|
+
orgSearch() {
|
|
18673
|
+
return createOrgSearchApi({ request: (path, init) => this.request(path, { boundary: "api", ...init ?? {} }) });
|
|
18674
|
+
}
|
|
17535
18675
|
async getCompanyHappyCredentials(companyId) {
|
|
17536
18676
|
return this.request(`/api/companies/${encodeURIComponent(companyId)}/happy-credentials`, { boundary: "api" });
|
|
17537
18677
|
}
|
|
@@ -17668,6 +18808,45 @@ class ApiClient {
|
|
|
17668
18808
|
const data = await this.request(`/v1/projects/${encodeURIComponent(projectId)}/automation-target`, { boundary: "sync" });
|
|
17669
18809
|
return parseServerAutomationTarget(data.target);
|
|
17670
18810
|
}
|
|
18811
|
+
sessionFollowupPath(projectId, followupId) {
|
|
18812
|
+
const root = `/v1/projects/${encodeURIComponent(projectId)}/session-followups`;
|
|
18813
|
+
return followupId ? `${root}/${encodeURIComponent(followupId)}` : root;
|
|
18814
|
+
}
|
|
18815
|
+
async listSessionFollowups(projectId, sessionId) {
|
|
18816
|
+
const query = sessionId ? `?session_id=${encodeURIComponent(sessionId)}&limit=100` : "?limit=100";
|
|
18817
|
+
return parseSessionFollowupList(await this.request(`${this.sessionFollowupPath(projectId)}${query}`, { boundary: "sync" }));
|
|
18818
|
+
}
|
|
18819
|
+
async createSessionFollowup(projectId, input) {
|
|
18820
|
+
return parseSessionFollowupPublic(await this.request(this.sessionFollowupPath(projectId), {
|
|
18821
|
+
method: "POST",
|
|
18822
|
+
boundary: "sync",
|
|
18823
|
+
body: JSON.stringify(validateSessionFollowupCreateInput(input))
|
|
18824
|
+
}));
|
|
18825
|
+
}
|
|
18826
|
+
async mutateSessionFollowupRevision(input) {
|
|
18827
|
+
const item = this.sessionFollowupPath(input.projectId, input.followupId);
|
|
18828
|
+
try {
|
|
18829
|
+
return parseSessionFollowupPublic(await this.request(input.action ? `${item}/${input.action}` : item, {
|
|
18830
|
+
method: input.method,
|
|
18831
|
+
boundary: "sync",
|
|
18832
|
+
body: JSON.stringify({ wireVersion: SESSION_FOLLOWUP_WIRE_VERSION, expectedRevision: input.expectedRevision })
|
|
18833
|
+
}));
|
|
18834
|
+
} catch (error) {
|
|
18835
|
+
if (!(error instanceof ApiError) || error.status !== 409) throw error;
|
|
18836
|
+
try {
|
|
18837
|
+
const body = JSON.parse(error.body);
|
|
18838
|
+
return parseSessionFollowupPublic(body.latest);
|
|
18839
|
+
} catch {
|
|
18840
|
+
throw error;
|
|
18841
|
+
}
|
|
18842
|
+
}
|
|
18843
|
+
}
|
|
18844
|
+
async stopSessionFollowup(projectId, followupId, expectedRevision) {
|
|
18845
|
+
return this.mutateSessionFollowupRevision({ projectId, followupId, expectedRevision, method: "POST", action: "stop" });
|
|
18846
|
+
}
|
|
18847
|
+
async deleteSessionFollowup(projectId, followupId, expectedRevision) {
|
|
18848
|
+
return this.mutateSessionFollowupRevision({ projectId, followupId, expectedRevision, method: "DELETE" });
|
|
18849
|
+
}
|
|
17671
18850
|
async setServerAutomationViewerKey(projectId, input) {
|
|
17672
18851
|
return this.request(`/v1/projects/${encodeURIComponent(projectId)}/automation-viewer-key`, {
|
|
17673
18852
|
method: "PUT",
|
|
@@ -18168,13 +19347,16 @@ class ApiClient {
|
|
|
18168
19347
|
async getMachines(companyId, candidateKeys = [], options = {}) {
|
|
18169
19348
|
const company = companyId ?? "personal";
|
|
18170
19349
|
const statusParam = options.withStatus ? "&withStatus=1" : "";
|
|
19350
|
+
const dekGeneration = machineDekRegistryGeneration();
|
|
18171
19351
|
const data = await this.request(
|
|
18172
19352
|
`/api/machines/list?company=${encodeURIComponent(company)}${statusParam}`,
|
|
18173
19353
|
{ boundary: "api" }
|
|
18174
19354
|
);
|
|
18175
19355
|
const rows = Array.isArray(data) ? data : data.machines ?? [];
|
|
18176
19356
|
const registry = await this.fetchMachineRegistry(rows.map((row2) => row2.id));
|
|
18177
|
-
|
|
19357
|
+
const enriched = rows.map((row2) => withMachineRegistry(row2, registry));
|
|
19358
|
+
const dataKeyFields = await decryptMachineDataKeyRows(enriched, candidateKeys, options, company, dekGeneration);
|
|
19359
|
+
return enriched.map((row2, index) => normalizeMachine(row2, candidateKeys, dataKeyFields[index]));
|
|
18178
19360
|
}
|
|
18179
19361
|
/**
|
|
18180
19362
|
* Fail-open: a registry outage degrades machine names back to the daemon
|
|
@@ -18336,23 +19518,25 @@ class ApiClient {
|
|
|
18336
19518
|
});
|
|
18337
19519
|
}
|
|
18338
19520
|
async fetchSessions(candidateKeys = [], tokenOverride, accountPrivateKey = null) {
|
|
19521
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
18339
19522
|
const data = await this.request("/v1/sessions", {
|
|
18340
19523
|
boundary: "sync",
|
|
18341
19524
|
tokenOverride
|
|
18342
19525
|
});
|
|
18343
19526
|
const rows = Array.isArray(data) ? data : data.sessions ?? [];
|
|
18344
|
-
await preDecryptSessionHeaders(rows, accountPrivateKey);
|
|
19527
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
18345
19528
|
return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
|
|
18346
19529
|
}
|
|
18347
19530
|
async fetchSessionsPage(candidateKeys = [], cursor, limit = 200, tokenOverride, accountPrivateKey = null) {
|
|
18348
19531
|
const params = new URLSearchParams({ limit: String(limit) });
|
|
18349
19532
|
if (cursor) params.set("cursor", cursor);
|
|
19533
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
18350
19534
|
const data = await this.request(
|
|
18351
19535
|
`/v2/sessions?${params.toString()}`,
|
|
18352
19536
|
{ boundary: "sync", tokenOverride }
|
|
18353
19537
|
);
|
|
18354
19538
|
const rows = data.sessions ?? [];
|
|
18355
|
-
await preDecryptSessionHeaders(rows, accountPrivateKey);
|
|
19539
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
18356
19540
|
return {
|
|
18357
19541
|
sessions: rows.map((row2) => apiSessionToSession(row2, candidateKeys)),
|
|
18358
19542
|
nextCursor: data.nextCursor ?? null,
|
|
@@ -18370,8 +19554,9 @@ class ApiClient {
|
|
|
18370
19554
|
return data.sessions ?? [];
|
|
18371
19555
|
}
|
|
18372
19556
|
async lookupSessions(candidateKeys, ids, tokenOverride, accountPrivateKey = null) {
|
|
19557
|
+
const boundaryGeneration2 = sessionDekRegistryGeneration();
|
|
18373
19558
|
const rows = await this.lookupApiSessions(ids, tokenOverride);
|
|
18374
|
-
await preDecryptSessionHeaders(rows, accountPrivateKey);
|
|
19559
|
+
await preDecryptSessionHeaders(rows, accountPrivateKey, boundaryGeneration2);
|
|
18375
19560
|
return rows.map((row2) => apiSessionToSession(row2, candidateKeys));
|
|
18376
19561
|
}
|
|
18377
19562
|
async deleteSession(sessionId, tokenOverride) {
|
|
@@ -18495,6 +19680,22 @@ class ApiClient {
|
|
|
18495
19680
|
message: typeof raw.message === "string" ? raw.message : null
|
|
18496
19681
|
};
|
|
18497
19682
|
}
|
|
19683
|
+
/** specs/desktop-pr-auto-merge-toggle — aplus-dev-studio#3315 server endpoint. */
|
|
19684
|
+
async setGithubPullRequestAutoMerge(input, tokenOverride) {
|
|
19685
|
+
const raw = await this.request(
|
|
19686
|
+
`/api/projects/${encodeURIComponent(input.projectId)}/pull-requests/${encodeURIComponent(String(input.number))}/auto-merge`,
|
|
19687
|
+
{
|
|
19688
|
+
method: "POST",
|
|
19689
|
+
boundary: "api",
|
|
19690
|
+
tokenOverride,
|
|
19691
|
+
body: JSON.stringify({ enabled: input.enabled, ...input.method ? { method: input.method } : {} })
|
|
19692
|
+
}
|
|
19693
|
+
);
|
|
19694
|
+
return {
|
|
19695
|
+
autoMerge: raw.autoMerge === true,
|
|
19696
|
+
prNumber: typeof raw.prNumber === "number" ? raw.prNumber : input.number
|
|
19697
|
+
};
|
|
19698
|
+
}
|
|
18498
19699
|
async fetchMessages(sessionId, afterSeq, tokenOverride, signal) {
|
|
18499
19700
|
const params = new URLSearchParams({ limit: "100" });
|
|
18500
19701
|
if (afterSeq !== void 0) params.set("after_seq", String(afterSeq));
|
|
@@ -18667,15 +19868,19 @@ class ApiClient {
|
|
|
18667
19868
|
});
|
|
18668
19869
|
}
|
|
18669
19870
|
async syncOwnerWorkspace(projectId, input = {}) {
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
|
|
18676
|
-
|
|
18677
|
-
|
|
18678
|
-
|
|
19871
|
+
const data = await this.request(
|
|
19872
|
+
`/api/projects/${encodeURIComponent(projectId)}/sync-owner-workspace`,
|
|
19873
|
+
{
|
|
19874
|
+
method: "POST",
|
|
19875
|
+
boundary: "api",
|
|
19876
|
+
timeoutMs: LONG_REQUEST_TIMEOUT_MS,
|
|
19877
|
+
body: JSON.stringify({
|
|
19878
|
+
trigger: input.trigger ?? "owner-manual",
|
|
19879
|
+
...input.vcsTarget ? { vcsTarget: input.vcsTarget } : {}
|
|
19880
|
+
})
|
|
19881
|
+
}
|
|
19882
|
+
);
|
|
19883
|
+
return { result: data.result, warnings: parseOwnerSyncWarnings(data.warnings) };
|
|
18679
19884
|
}
|
|
18680
19885
|
async getProjectVcsRemote(projectId) {
|
|
18681
19886
|
const data = await this.request(
|
|
@@ -19251,8 +20456,8 @@ function happyHomeOrder(input) {
|
|
|
19251
20456
|
if (!runtime) return [...homes];
|
|
19252
20457
|
return [runtime, ...homes.filter((source) => source !== runtime)];
|
|
19253
20458
|
}
|
|
19254
|
-
function selectFirstValidCredential(
|
|
19255
|
-
for (const candidate of
|
|
20459
|
+
function selectFirstValidCredential(candidates2, readFile2) {
|
|
20460
|
+
for (const candidate of candidates2) {
|
|
19256
20461
|
const raw = readFile2(candidate.path);
|
|
19257
20462
|
if (raw === null) continue;
|
|
19258
20463
|
const credential = parseAccessKeyFile(raw);
|
|
@@ -19408,7 +20613,7 @@ class RealtimeSocket {
|
|
|
19408
20613
|
}
|
|
19409
20614
|
const schedule = connectSchedule(totalTimeoutMs);
|
|
19410
20615
|
let lastError = null;
|
|
19411
|
-
for (let
|
|
20616
|
+
for (let attempt2 = 0; attempt2 < CONNECT_ATTEMPTS; attempt2 += 1) {
|
|
19412
20617
|
const result = await this.connectOnce(schedule.attemptTimeoutMs);
|
|
19413
20618
|
if (result.type === "cancelled") throw this.cancelledError();
|
|
19414
20619
|
if (result.type === "connected") {
|
|
@@ -19418,7 +20623,7 @@ class RealtimeSocket {
|
|
|
19418
20623
|
}
|
|
19419
20624
|
if (result.type === "error") lastError = result.error;
|
|
19420
20625
|
this.socket.disconnect();
|
|
19421
|
-
const retryDelay = schedule.retryDelaysMs[
|
|
20626
|
+
const retryDelay = schedule.retryDelaysMs[attempt2];
|
|
19422
20627
|
if (retryDelay !== void 0 && retryDelay > 0 && !await this.waitForRetry(retryDelay)) {
|
|
19423
20628
|
throw this.cancelledError();
|
|
19424
20629
|
}
|
|
@@ -19927,8 +21132,19 @@ async function runLogin(out, env = process.env) {
|
|
|
19927
21132
|
const client = new ApiClient({ apiBaseUrl: serverUrl, syncBaseUrl: serverUrl });
|
|
19928
21133
|
let result;
|
|
19929
21134
|
try {
|
|
19930
|
-
|
|
19931
|
-
|
|
21135
|
+
const outcome = await client.login({ email, password });
|
|
21136
|
+
if (outcome.kind === "mfa-required") {
|
|
21137
|
+
if (outcome.challenge.enrollmentRequired || !outcome.challenge.methods.includes("totp")) {
|
|
21138
|
+
throw new CliError("This account requires MFA enrollment or a security key. Sign in on the web first, then retry.");
|
|
21139
|
+
}
|
|
21140
|
+
const code = (await askVisible("MFA code (6 digits): ")).trim();
|
|
21141
|
+
if (!/^\d{6}$/.test(code)) throw new CliError("The MFA code must be 6 digits.");
|
|
21142
|
+
result = await client.verifyMfaTotpLogin({ loginToken: outcome.challenge.loginToken, code });
|
|
21143
|
+
} else {
|
|
21144
|
+
result = outcome.result;
|
|
21145
|
+
}
|
|
21146
|
+
} catch (error) {
|
|
21147
|
+
if (error instanceof CliError) throw error;
|
|
19932
21148
|
throw new CliError("Login failed. Check the email and password, then try again.");
|
|
19933
21149
|
}
|
|
19934
21150
|
if (!result?.token || !result?.secret) throw new CliError("The server did not return usable credentials.");
|
|
@@ -20553,12 +21769,12 @@ const codexUltraEffortOptions = [
|
|
|
20553
21769
|
function getEffortOptionsForAgent(agent, model) {
|
|
20554
21770
|
if (model === "default") return [];
|
|
20555
21771
|
if (agent === "codex") {
|
|
20556
|
-
if (model === "gpt-5.6-sol" || model === "gpt-5.6-terra") return codexUltraEffortOptions;
|
|
21772
|
+
if (model === "gpt-6-astra" || model === "gpt-5.6-sol" || model === "gpt-5.6-terra") return codexUltraEffortOptions;
|
|
20557
21773
|
if (model === "gpt-5.6-luna") return codexMaxEffortOptions;
|
|
20558
21774
|
return codexEffortOptions;
|
|
20559
21775
|
}
|
|
20560
21776
|
if (agent === "gemini" || agent === "opencode" || agent === "grok") return [];
|
|
20561
|
-
if (model === "claude-fable-5") return claudeFableEffortOptions;
|
|
21777
|
+
if (model === "claude-fable-5-1") return claudeFableEffortOptions;
|
|
20562
21778
|
return claudeEffortOptions;
|
|
20563
21779
|
}
|
|
20564
21780
|
function defaultEffortForAgent(agent, model) {
|
|
@@ -20566,7 +21782,7 @@ function defaultEffortForAgent(agent, model) {
|
|
|
20566
21782
|
if (options.length === 0) return null;
|
|
20567
21783
|
if (agent === "codex") {
|
|
20568
21784
|
if (model === "gpt-5.6-sol") return "low";
|
|
20569
|
-
if (model === "gpt-5.6-terra" || model === "gpt-5.6-luna") return "medium";
|
|
21785
|
+
if (model === "gpt-6-astra" || model === "gpt-5.6-terra" || model === "gpt-5.6-luna") return "medium";
|
|
20570
21786
|
}
|
|
20571
21787
|
return options.some((option) => option.key === "high") ? "high" : options[options.length - 1].key;
|
|
20572
21788
|
}
|
|
@@ -20578,13 +21794,14 @@ const newSessionAgents = [
|
|
|
20578
21794
|
];
|
|
20579
21795
|
const claudeModelOptions = [
|
|
20580
21796
|
{ key: "default", label: "Default" },
|
|
20581
|
-
{ key: "claude-fable-5", label: "Fable 5" },
|
|
21797
|
+
{ key: "claude-fable-5-1", label: "Fable 5.1" },
|
|
20582
21798
|
{ key: "claude-opus-5", label: "Opus 5" },
|
|
20583
21799
|
{ key: "claude-sonnet-5", label: "Sonnet 5" },
|
|
20584
21800
|
{ key: "claude-haiku-4-5", label: "Haiku 4.5" }
|
|
20585
21801
|
];
|
|
20586
21802
|
const codexModelOptions = [
|
|
20587
21803
|
{ key: "default", label: "Default" },
|
|
21804
|
+
{ key: "gpt-6-astra", label: "GPT-6 Astra" },
|
|
20588
21805
|
{ key: "gpt-5.6-sol", label: "GPT-5.6 Sol" },
|
|
20589
21806
|
{ key: "gpt-5.6-terra", label: "GPT-5.6 Terra" },
|
|
20590
21807
|
{ key: "gpt-5.6-luna", label: "GPT-5.6 Luna" },
|
|
@@ -20617,10 +21834,18 @@ function getModelsForAgent(agent) {
|
|
|
20617
21834
|
if (agent === "grok") return grokModelOptions;
|
|
20618
21835
|
return claudeModelOptions;
|
|
20619
21836
|
}
|
|
21837
|
+
const RETIRED_MODEL_KEYS = {
|
|
21838
|
+
"claude-fable-5": "claude-fable-5-1"
|
|
21839
|
+
};
|
|
21840
|
+
function isKnownModelForAgent(agent, model) {
|
|
21841
|
+
const current = RETIRED_MODEL_KEYS[model] ?? model;
|
|
21842
|
+
return getModelsForAgent(agent).some((option) => option.key === current);
|
|
21843
|
+
}
|
|
20620
21844
|
function normalizeModelForAgent(agent, model) {
|
|
20621
21845
|
if (agent === "opencode") return model || "default";
|
|
21846
|
+
const current = RETIRED_MODEL_KEYS[model] ?? model;
|
|
20622
21847
|
const models = getModelsForAgent(agent);
|
|
20623
|
-
return models.some((option) => option.key ===
|
|
21848
|
+
return models.some((option) => option.key === current) ? current : models[0]?.key ?? "default";
|
|
20624
21849
|
}
|
|
20625
21850
|
function normalizeModelSelectionOptions(agent, current, patch) {
|
|
20626
21851
|
const model = normalizeModelForAgent(agent, patch.model ?? current.model);
|
|
@@ -20675,7 +21900,7 @@ function updateLiveChatSettings(current, patch) {
|
|
|
20675
21900
|
}
|
|
20676
21901
|
let selection = { model: current.model, effort: current.effort };
|
|
20677
21902
|
if (patch.model !== void 0) {
|
|
20678
|
-
if (!
|
|
21903
|
+
if (!isKnownModelForAgent(current.agent, patch.model)) return current;
|
|
20679
21904
|
selection = normalizeModelSelectionOptions(current.agent, selection, { model: patch.model });
|
|
20680
21905
|
}
|
|
20681
21906
|
if (patch.effort !== void 0) {
|
|
@@ -20758,6 +21983,141 @@ function resumeHistoryCount(input) {
|
|
|
20758
21983
|
if (!input.resuming) return 0;
|
|
20759
21984
|
return input.since ?? DEFAULT_RESUME_HISTORY;
|
|
20760
21985
|
}
|
|
21986
|
+
const AGENT_SPAWN_BUDGET_MIN = 4;
|
|
21987
|
+
const AGENT_SPAWN_BUDGET_MAX = 32;
|
|
21988
|
+
const MEMORY_BYTES_PER_AGENT = 2 * 1024 ** 3;
|
|
21989
|
+
const AGENTS_PER_CORE = 2;
|
|
21990
|
+
function machineSpawnBudget(capacity) {
|
|
21991
|
+
const cores = Number.isFinite(capacity.cpuCount) ? Math.max(0, Math.floor(capacity.cpuCount)) : 0;
|
|
21992
|
+
const memory = Number.isFinite(capacity.totalMemoryBytes) ? Math.max(0, capacity.totalMemoryBytes) : 0;
|
|
21993
|
+
if (cores <= 0 || memory <= 0) return AGENT_SPAWN_BUDGET_MIN;
|
|
21994
|
+
const budget = Math.min(Math.floor(memory / MEMORY_BYTES_PER_AGENT), cores * AGENTS_PER_CORE);
|
|
21995
|
+
return Math.min(Math.max(budget, AGENT_SPAWN_BUDGET_MIN), AGENT_SPAWN_BUDGET_MAX);
|
|
21996
|
+
}
|
|
21997
|
+
function tierSpawnBudget(depth, machineBudget) {
|
|
21998
|
+
return depth <= 0 ? machineBudget : Math.ceil(machineBudget / 2);
|
|
21999
|
+
}
|
|
22000
|
+
function resolveMaxSpawn(input) {
|
|
22001
|
+
const tier = tierSpawnBudget(input.depth, input.machineBudget);
|
|
22002
|
+
return input.policy === null ? tier : Math.min(input.policy, tier);
|
|
22003
|
+
}
|
|
22004
|
+
const SPAWN_BUDGET_WINDOW_MS = 6 * 60 * 60 * 1e3;
|
|
22005
|
+
function isCharged(chargedAt, now) {
|
|
22006
|
+
return chargedAt > now - SPAWN_BUDGET_WINDOW_MS;
|
|
22007
|
+
}
|
|
22008
|
+
function chargeTimes(records) {
|
|
22009
|
+
const byId = /* @__PURE__ */ new Map();
|
|
22010
|
+
const unkeyed = [];
|
|
22011
|
+
for (const record2 of records) {
|
|
22012
|
+
if (record2.spawnId === void 0) {
|
|
22013
|
+
unkeyed.push(record2.ts);
|
|
22014
|
+
continue;
|
|
22015
|
+
}
|
|
22016
|
+
const seen = byId.get(record2.spawnId);
|
|
22017
|
+
if (seen === void 0 || record2.ts < seen) byId.set(record2.spawnId, record2.ts);
|
|
22018
|
+
}
|
|
22019
|
+
return [...byId.values(), ...unkeyed];
|
|
22020
|
+
}
|
|
22021
|
+
function chargedSpawnCount(records, now) {
|
|
22022
|
+
return chargeTimes(records).filter((chargedAt) => isCharged(chargedAt, now)).length;
|
|
22023
|
+
}
|
|
22024
|
+
function spawnBudgetResetsAt(records, now, maxSpawn) {
|
|
22025
|
+
if (maxSpawn <= 0) return null;
|
|
22026
|
+
const expiries = chargeTimes(records).filter((chargedAt) => isCharged(chargedAt, now)).map((ts) => ts + SPAWN_BUDGET_WINDOW_MS).sort((a, b) => a - b);
|
|
22027
|
+
if (expiries.length < maxSpawn) return null;
|
|
22028
|
+
return expiries[expiries.length - maxSpawn];
|
|
22029
|
+
}
|
|
22030
|
+
function spawnLimitDetail(input) {
|
|
22031
|
+
const used = `${input.spawned}/${input.maxSpawn} used`;
|
|
22032
|
+
if (input.resetsAt === null || input.resetsAt <= input.now) return used;
|
|
22033
|
+
const minutes = Math.ceil((input.resetsAt - input.now) / 6e4);
|
|
22034
|
+
return `${used}; the next slot frees in ${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
22035
|
+
}
|
|
22036
|
+
const AGENT_ENV_FLAG = "SAYCODE_AGENT_ENV";
|
|
22037
|
+
const AGENT_MAX_DEPTH = 2;
|
|
22038
|
+
function readOptionalNonNegativeInt(raw) {
|
|
22039
|
+
if (raw === void 0 || !/^\d+$/.test(raw.trim())) return null;
|
|
22040
|
+
const value2 = Number(raw.trim());
|
|
22041
|
+
return Number.isSafeInteger(value2) ? value2 : null;
|
|
22042
|
+
}
|
|
22043
|
+
function normalizeDirectory(raw) {
|
|
22044
|
+
const trimmed = raw.trim();
|
|
22045
|
+
if (!trimmed) return "";
|
|
22046
|
+
const stripped = trimmed.replace(/\/+$/, "");
|
|
22047
|
+
return stripped || "/";
|
|
22048
|
+
}
|
|
22049
|
+
function hasAgentMarker(env) {
|
|
22050
|
+
return env[AGENT_ENV_FLAG] === "1";
|
|
22051
|
+
}
|
|
22052
|
+
function isAgentEnv(env) {
|
|
22053
|
+
return hasAgentMarker(env) && normalizeDirectory(env.SAYCODE_AGENT_ROOT ?? "") !== "";
|
|
22054
|
+
}
|
|
22055
|
+
function readAgentEnv(env, machineBudget) {
|
|
22056
|
+
if (!isAgentEnv(env)) return null;
|
|
22057
|
+
const root = normalizeDirectory(env.SAYCODE_AGENT_ROOT ?? "");
|
|
22058
|
+
const depth = readOptionalNonNegativeInt(env.SAYCODE_AGENT_DEPTH) ?? 0;
|
|
22059
|
+
return {
|
|
22060
|
+
root,
|
|
22061
|
+
scope: normalizeDirectory(env.SAYCODE_AGENT_SCOPE ?? "") || root,
|
|
22062
|
+
depth,
|
|
22063
|
+
maxSpawn: resolveMaxSpawn({
|
|
22064
|
+
policy: readOptionalNonNegativeInt(env.SAYCODE_AGENT_MAX_SPAWN),
|
|
22065
|
+
depth,
|
|
22066
|
+
machineBudget
|
|
22067
|
+
}),
|
|
22068
|
+
agentId: env.SAYCODE_AGENT_ID?.trim() || null,
|
|
22069
|
+
sessionId: env.APLUS_SESSION_ID?.trim() || null
|
|
22070
|
+
};
|
|
22071
|
+
}
|
|
22072
|
+
function spawnDecision(input) {
|
|
22073
|
+
if (input.depth >= AGENT_MAX_DEPTH) return "spawn_depth_exceeded";
|
|
22074
|
+
if (input.spawned >= input.maxSpawn) return "spawn_limit_exceeded";
|
|
22075
|
+
return "allow";
|
|
22076
|
+
}
|
|
22077
|
+
function childAgentEnv(parent, childAgentId) {
|
|
22078
|
+
return {
|
|
22079
|
+
[AGENT_ENV_FLAG]: "1",
|
|
22080
|
+
SAYCODE_AGENT_ROOT: parent.root,
|
|
22081
|
+
SAYCODE_AGENT_SCOPE: parent.scope,
|
|
22082
|
+
SAYCODE_AGENT_DEPTH: String(parent.depth + 1),
|
|
22083
|
+
SAYCODE_AGENT_MAX_SPAWN: String(tierSpawnBudget(parent.depth + 1, parent.maxSpawn)),
|
|
22084
|
+
SAYCODE_AGENT_ID: childAgentId
|
|
22085
|
+
};
|
|
22086
|
+
}
|
|
22087
|
+
function isInScope(session, scope) {
|
|
22088
|
+
if (!session.machineId || !scope.machineId) return false;
|
|
22089
|
+
if (session.machineId !== scope.machineId) return false;
|
|
22090
|
+
const root = normalizeDirectory(scope.root);
|
|
22091
|
+
const directory = normalizeDirectory(session.directory);
|
|
22092
|
+
if (!root || !directory) return false;
|
|
22093
|
+
if (directory === root) return true;
|
|
22094
|
+
return directory.startsWith(root === "/" ? "/" : `${root}/`);
|
|
22095
|
+
}
|
|
22096
|
+
function sessionsInScope(sessions, scope) {
|
|
22097
|
+
return sessions.filter((session) => isInScope(session, scope));
|
|
22098
|
+
}
|
|
22099
|
+
function machineSettingsPaths(input) {
|
|
22100
|
+
const override = input.env.SAYCODE_HAPPY_HOME?.trim();
|
|
22101
|
+
const runtimeHome = input.env.HAPPY_HOME_DIR?.trim();
|
|
22102
|
+
const homes = CREDENTIAL_SOURCE_ORDER.filter((source) => source !== "saycode").map((source) => `${input.homeDir}/.${source}`);
|
|
22103
|
+
const ordered = [input.credentialHome?.trim(), override, runtimeHome, ...homes];
|
|
22104
|
+
return [...new Set(ordered.filter((home) => Boolean(home)))].map((home) => `${home}/settings.json`.replace(/\/{2,}/g, "/"));
|
|
22105
|
+
}
|
|
22106
|
+
function readMachineId(input, readFile2) {
|
|
22107
|
+
for (const path of machineSettingsPaths(input)) {
|
|
22108
|
+
const raw = readFile2(path);
|
|
22109
|
+
if (!raw) continue;
|
|
22110
|
+
try {
|
|
22111
|
+
const parsed = JSON.parse(raw);
|
|
22112
|
+
if (typeof parsed?.machineId === "string" && parsed.machineId) return parsed.machineId;
|
|
22113
|
+
} catch {
|
|
22114
|
+
}
|
|
22115
|
+
}
|
|
22116
|
+
return null;
|
|
22117
|
+
}
|
|
22118
|
+
function spawnBudgetKey(env) {
|
|
22119
|
+
return env.agentId ?? env.root;
|
|
22120
|
+
}
|
|
20761
22121
|
const REAL_CLI_SESSION_CONNECTION_DEPS = {
|
|
20762
22122
|
createLocalId: () => `saycode-cli-${process.pid}-${Math.random().toString(36).slice(2)}`
|
|
20763
22123
|
};
|
|
@@ -21573,14 +22933,12 @@ function hasNewConversationSettings(request) {
|
|
|
21573
22933
|
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;
|
|
21574
22934
|
}
|
|
21575
22935
|
function validateExplicitSelection(request, agent) {
|
|
21576
|
-
if (request.model !== void 0 && !
|
|
22936
|
+
if (request.model !== void 0 && !isKnownModelForAgent(agent, request.model)) {
|
|
21577
22937
|
throw new CliError(`Unknown ${agent} model "${request.model}".`);
|
|
21578
22938
|
}
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
|
|
21582
|
-
throw new CliError(`Effort "${request.effort}" is not available for ${agent} model "${model}".`);
|
|
21583
|
-
}
|
|
22939
|
+
const model = request.model === void 0 ? DEFAULT_CLI_NEW_CONVERSATION_OPTIONS.model : normalizeModelForAgent(agent, request.model);
|
|
22940
|
+
if (request.effort !== void 0 && !effortChoices(agent, model).some((option) => option.key === request.effort)) {
|
|
22941
|
+
throw new CliError(`Effort "${request.effort}" is not available for ${agent} model "${model}".`);
|
|
21584
22942
|
}
|
|
21585
22943
|
}
|
|
21586
22944
|
async function resolveCliNewConversationOptions(request, ask) {
|
|
@@ -22527,7 +23885,8 @@ function normalizeSessionRows(rows, candidateKeys) {
|
|
|
22527
23885
|
directory: readString(metadata, "path") ?? "",
|
|
22528
23886
|
summary: readSummary(metadata),
|
|
22529
23887
|
active: row2.active === true,
|
|
22530
|
-
updatedAt: row2.updatedAt ?? 0
|
|
23888
|
+
updatedAt: row2.updatedAt ?? 0,
|
|
23889
|
+
agentState: decryptJsonWithCandidates(row2.agentState, candidateKeys)
|
|
22531
23890
|
});
|
|
22532
23891
|
}
|
|
22533
23892
|
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
@@ -22556,9 +23915,9 @@ async function resolveMachineId(context, query) {
|
|
|
22556
23915
|
const match = matchMachine(machines, query);
|
|
22557
23916
|
if (match.type === "match") return match.machine.id;
|
|
22558
23917
|
if (match.type === "not-found") throw new CliError(`No machine matches "${query}". Run \`saycode machines\` to list them.`);
|
|
22559
|
-
const
|
|
23918
|
+
const candidates2 = match.candidates.map((machine) => ` ${machine.host} (${machine.id.slice(0, 8)})`).join("\n");
|
|
22560
23919
|
throw new CliError(`"${query}" matches more than one machine:
|
|
22561
|
-
${
|
|
23920
|
+
${candidates2}
|
|
22562
23921
|
|
|
22563
23922
|
Use a longer prefix or the machine id.`);
|
|
22564
23923
|
}
|
|
@@ -22837,6 +24196,9 @@ async function askTemporaryLine(question) {
|
|
|
22837
24196
|
}
|
|
22838
24197
|
async function runChat(context, options, providedTransport) {
|
|
22839
24198
|
const resuming = !!options.sessionId;
|
|
24199
|
+
if (!resuming && hasAgentMarker(process.env)) {
|
|
24200
|
+
throw new CliError("This session may not start a new conversation with `chat`. Use `saycode agent spawn`, which applies the spawn budget and hands the child its lineage. `chat <session-id>` to attach still works.");
|
|
24201
|
+
}
|
|
22840
24202
|
let optionPrompter = null;
|
|
22841
24203
|
let optionInput = null;
|
|
22842
24204
|
if (!resuming && options.promptForOptions) {
|
|
@@ -23741,6 +25103,9 @@ function waitForSessionState(options) {
|
|
|
23741
25103
|
});
|
|
23742
25104
|
}
|
|
23743
25105
|
const AGENT_FAILURE_EXIT_CODE = 3;
|
|
25106
|
+
function isAgentFailure(value2) {
|
|
25107
|
+
return typeof value2 === "object" && value2 !== null && "stdout" in value2;
|
|
25108
|
+
}
|
|
23744
25109
|
function line(value2) {
|
|
23745
25110
|
return `${JSON.stringify(value2)}
|
|
23746
25111
|
`;
|
|
@@ -23873,82 +25238,6 @@ async function promptTarget(runtime, target, text2, options) {
|
|
|
23873
25238
|
stream2.close();
|
|
23874
25239
|
}
|
|
23875
25240
|
}
|
|
23876
|
-
const AGENT_ENV_FLAG = "SAYCODE_AGENT_ENV";
|
|
23877
|
-
const AGENT_MAX_DEPTH = 2;
|
|
23878
|
-
const DEFAULT_AGENT_MAX_SPAWN = 4;
|
|
23879
|
-
const AGENT_MAX_SPAWN_CEILING = 16;
|
|
23880
|
-
function readNonNegativeInt(raw, fallback) {
|
|
23881
|
-
if (raw === void 0) return fallback;
|
|
23882
|
-
if (!/^\d+$/.test(raw.trim())) return fallback;
|
|
23883
|
-
const value2 = Number(raw.trim());
|
|
23884
|
-
return Number.isSafeInteger(value2) ? value2 : fallback;
|
|
23885
|
-
}
|
|
23886
|
-
function normalizeDirectory(raw) {
|
|
23887
|
-
const trimmed = raw.trim();
|
|
23888
|
-
if (!trimmed) return "";
|
|
23889
|
-
const stripped = trimmed.replace(/\/+$/, "");
|
|
23890
|
-
return stripped || "/";
|
|
23891
|
-
}
|
|
23892
|
-
function readAgentEnv(env) {
|
|
23893
|
-
if (env[AGENT_ENV_FLAG] !== "1") return null;
|
|
23894
|
-
const root = normalizeDirectory(env.SAYCODE_AGENT_ROOT ?? "");
|
|
23895
|
-
if (!root) return null;
|
|
23896
|
-
return {
|
|
23897
|
-
root,
|
|
23898
|
-
depth: readNonNegativeInt(env.SAYCODE_AGENT_DEPTH, 0),
|
|
23899
|
-
maxSpawn: Math.min(
|
|
23900
|
-
readNonNegativeInt(env.SAYCODE_AGENT_MAX_SPAWN, DEFAULT_AGENT_MAX_SPAWN),
|
|
23901
|
-
AGENT_MAX_SPAWN_CEILING
|
|
23902
|
-
),
|
|
23903
|
-
agentId: env.SAYCODE_AGENT_ID?.trim() || null,
|
|
23904
|
-
sessionId: env.APLUS_SESSION_ID?.trim() || null
|
|
23905
|
-
};
|
|
23906
|
-
}
|
|
23907
|
-
function spawnDecision(input) {
|
|
23908
|
-
if (input.depth >= AGENT_MAX_DEPTH) return "spawn_depth_exceeded";
|
|
23909
|
-
if (input.spawned >= input.maxSpawn) return "spawn_limit_exceeded";
|
|
23910
|
-
return "allow";
|
|
23911
|
-
}
|
|
23912
|
-
function childAgentEnv(parent, childAgentId) {
|
|
23913
|
-
return {
|
|
23914
|
-
[AGENT_ENV_FLAG]: "1",
|
|
23915
|
-
SAYCODE_AGENT_ROOT: parent.root,
|
|
23916
|
-
SAYCODE_AGENT_DEPTH: String(parent.depth + 1),
|
|
23917
|
-
SAYCODE_AGENT_MAX_SPAWN: String(parent.maxSpawn),
|
|
23918
|
-
SAYCODE_AGENT_ID: childAgentId
|
|
23919
|
-
};
|
|
23920
|
-
}
|
|
23921
|
-
function isInScope(session, scope) {
|
|
23922
|
-
if (!session.machineId || !scope.machineId) return false;
|
|
23923
|
-
if (session.machineId !== scope.machineId) return false;
|
|
23924
|
-
const root = normalizeDirectory(scope.root);
|
|
23925
|
-
const directory = normalizeDirectory(session.directory);
|
|
23926
|
-
if (!root || !directory) return false;
|
|
23927
|
-
if (directory === root) return true;
|
|
23928
|
-
return directory.startsWith(root === "/" ? "/" : `${root}/`);
|
|
23929
|
-
}
|
|
23930
|
-
function machineSettingsPaths(input) {
|
|
23931
|
-
const override = input.env.SAYCODE_HAPPY_HOME?.trim();
|
|
23932
|
-
const runtimeHome = input.env.HAPPY_HOME_DIR?.trim();
|
|
23933
|
-
const homes = CREDENTIAL_SOURCE_ORDER.filter((source) => source !== "saycode").map((source) => `${input.homeDir}/.${source}`);
|
|
23934
|
-
const ordered = [input.credentialHome?.trim(), override, runtimeHome, ...homes];
|
|
23935
|
-
return [...new Set(ordered.filter((home) => Boolean(home)))].map((home) => `${home}/settings.json`.replace(/\/{2,}/g, "/"));
|
|
23936
|
-
}
|
|
23937
|
-
function readMachineId(input, readFile2) {
|
|
23938
|
-
for (const path of machineSettingsPaths(input)) {
|
|
23939
|
-
const raw = readFile2(path);
|
|
23940
|
-
if (!raw) continue;
|
|
23941
|
-
try {
|
|
23942
|
-
const parsed = JSON.parse(raw);
|
|
23943
|
-
if (typeof parsed?.machineId === "string" && parsed.machineId) return parsed.machineId;
|
|
23944
|
-
} catch {
|
|
23945
|
-
}
|
|
23946
|
-
}
|
|
23947
|
-
return null;
|
|
23948
|
-
}
|
|
23949
|
-
function spawnBudgetKey(env) {
|
|
23950
|
-
return env.agentId ?? env.root;
|
|
23951
|
-
}
|
|
23952
25241
|
function normalizeAbsolutePath(raw) {
|
|
23953
25242
|
const trimmed = raw.trim();
|
|
23954
25243
|
if (!trimmed.startsWith("/")) return "";
|
|
@@ -24059,6 +25348,71 @@ function buildAgentHandoffPrompt(source, context) {
|
|
|
24059
25348
|
...FIRST_ACTIONS.map((action) => `- ${action}`)
|
|
24060
25349
|
].join("\n");
|
|
24061
25350
|
}
|
|
25351
|
+
function checkSpawnBudget(runtime, env) {
|
|
25352
|
+
const key = spawnBudgetKey(env);
|
|
25353
|
+
let usage;
|
|
25354
|
+
try {
|
|
25355
|
+
usage = runtime.countSpawns(key, env.maxSpawn);
|
|
25356
|
+
} catch (error) {
|
|
25357
|
+
return agentFail("spawn_limit_exceeded", `spawn ledger is unreadable, refusing: ${String(error)}`);
|
|
25358
|
+
}
|
|
25359
|
+
const decision = spawnDecision({ depth: env.depth, maxSpawn: env.maxSpawn, spawned: usage.spawned });
|
|
25360
|
+
if (decision === "allow") return { spawned: usage.spawned, key };
|
|
25361
|
+
return agentFail(decision, spawnLimitDetail({
|
|
25362
|
+
spawned: usage.spawned,
|
|
25363
|
+
maxSpawn: env.maxSpawn,
|
|
25364
|
+
resetsAt: usage.resetsAt,
|
|
25365
|
+
now: runtime.now()
|
|
25366
|
+
}));
|
|
25367
|
+
}
|
|
25368
|
+
function reserveSpawn(runtime, env, key, attempt2, spawnedBefore) {
|
|
25369
|
+
const spawnId = runtime.newAgentId();
|
|
25370
|
+
try {
|
|
25371
|
+
runtime.recordSpawn({
|
|
25372
|
+
ts: runtime.now(),
|
|
25373
|
+
agentKey: key,
|
|
25374
|
+
root: env.scope,
|
|
25375
|
+
depth: env.depth,
|
|
25376
|
+
// Unknown until the daemon answers; `resolveSpawn` appends the id under
|
|
25377
|
+
// the same `spawnId`, and the counter charges the pair once.
|
|
25378
|
+
childSessionId: null,
|
|
25379
|
+
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
25380
|
+
agent: attempt2.agent,
|
|
25381
|
+
promptPreview: attempt2.promptPreview,
|
|
25382
|
+
outcome: "failed",
|
|
25383
|
+
spawnId
|
|
25384
|
+
});
|
|
25385
|
+
} catch (error) {
|
|
25386
|
+
return agentFail("spawn_limit_exceeded", `spawn ledger is unwritable, refusing before anything is created: ${String(error)}`);
|
|
25387
|
+
}
|
|
25388
|
+
try {
|
|
25389
|
+
if (runtime.countSpawns(key, env.maxSpawn).spawned <= spawnedBefore) {
|
|
25390
|
+
return agentFail("spawn_limit_exceeded", "the spawn reservation did not land in the ledger, refusing before anything is created");
|
|
25391
|
+
}
|
|
25392
|
+
} catch (error) {
|
|
25393
|
+
return agentFail("spawn_limit_exceeded", `spawn ledger became unreadable after reserving, refusing: ${String(error)}`);
|
|
25394
|
+
}
|
|
25395
|
+
return { spawnId };
|
|
25396
|
+
}
|
|
25397
|
+
function resolveSpawn(runtime, env, key, spawnId, outcome) {
|
|
25398
|
+
try {
|
|
25399
|
+
runtime.recordSpawn({
|
|
25400
|
+
ts: runtime.now(),
|
|
25401
|
+
agentKey: key,
|
|
25402
|
+
root: env.scope,
|
|
25403
|
+
depth: env.depth,
|
|
25404
|
+
childSessionId: outcome.sessionId,
|
|
25405
|
+
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
25406
|
+
agent: outcome.agent,
|
|
25407
|
+
promptPreview: outcome.promptPreview,
|
|
25408
|
+
outcome: outcome.sessionId ? "spawned" : "failed",
|
|
25409
|
+
spawnId
|
|
25410
|
+
});
|
|
25411
|
+
return true;
|
|
25412
|
+
} catch {
|
|
25413
|
+
return false;
|
|
25414
|
+
}
|
|
25415
|
+
}
|
|
24062
25416
|
async function fileSpawnedSessionUnderProject(runtime, sessionId, directory) {
|
|
24063
25417
|
if (!runtime.registerSpawnedSession) return;
|
|
24064
25418
|
try {
|
|
@@ -24104,15 +25458,9 @@ async function handoffToSibling(runtime, scope, env, handoff, agent, modelOption
|
|
|
24104
25458
|
if (env.depth >= AGENT_MAX_DEPTH) {
|
|
24105
25459
|
return agentFail("spawn_depth_exceeded", `depth ${env.depth} is already at the ceiling of ${AGENT_MAX_DEPTH}`);
|
|
24106
25460
|
}
|
|
24107
|
-
const
|
|
24108
|
-
|
|
24109
|
-
|
|
24110
|
-
spawned = runtime.countSpawns(key);
|
|
24111
|
-
} catch (error) {
|
|
24112
|
-
return agentFail("spawn_limit_exceeded", `spawn ledger is unreadable, refusing: ${String(error)}`);
|
|
24113
|
-
}
|
|
24114
|
-
const decision = spawnDecision({ depth: env.depth, maxSpawn: env.maxSpawn, spawned });
|
|
24115
|
-
if (decision !== "allow") return agentFail(decision, `${spawned}/${env.maxSpawn} used`);
|
|
25461
|
+
const budget = checkSpawnBudget(runtime, env);
|
|
25462
|
+
if (isAgentFailure(budget)) return budget;
|
|
25463
|
+
const { spawned, key } = budget;
|
|
24116
25464
|
let prompt;
|
|
24117
25465
|
try {
|
|
24118
25466
|
prompt = buildHandoffPrompt(await runtime.readMessages(from, HANDOFF_READ_MESSAGES), source, agent);
|
|
@@ -24120,6 +25468,9 @@ async function handoffToSibling(runtime, scope, env, handoff, agent, modelOption
|
|
|
24120
25468
|
return agentFail("handoff_context_failed", String(error));
|
|
24121
25469
|
}
|
|
24122
25470
|
const childId = runtime.newAgentId();
|
|
25471
|
+
const preview = `handoff from ${from}`;
|
|
25472
|
+
const reservation = reserveSpawn(runtime, env, key, { agent, promptPreview: preview }, spawned);
|
|
25473
|
+
if (isAgentFailure(reservation)) return reservation;
|
|
24123
25474
|
let sessionId = null;
|
|
24124
25475
|
let spawnError = null;
|
|
24125
25476
|
try {
|
|
@@ -24133,22 +25484,7 @@ async function handoffToSibling(runtime, scope, env, handoff, agent, modelOption
|
|
|
24133
25484
|
} catch (error) {
|
|
24134
25485
|
spawnError = error;
|
|
24135
25486
|
}
|
|
24136
|
-
|
|
24137
|
-
runtime.recordSpawn({
|
|
24138
|
-
ts: runtime.now(),
|
|
24139
|
-
agentKey: key,
|
|
24140
|
-
root: env.root,
|
|
24141
|
-
depth: env.depth,
|
|
24142
|
-
childSessionId: sessionId,
|
|
24143
|
-
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
24144
|
-
agent,
|
|
24145
|
-
promptPreview: `handoff from ${from}`,
|
|
24146
|
-
outcome: sessionId ? "spawned" : "failed"
|
|
24147
|
-
});
|
|
24148
|
-
} catch (error) {
|
|
24149
|
-
const created = sessionId ? `spawned ${sessionId} but ` : "";
|
|
24150
|
-
return agentFail("spawn_limit_exceeded", `${created}the ledger write failed: ${String(error)}`);
|
|
24151
|
-
}
|
|
25487
|
+
const lineageRecorded = resolveSpawn(runtime, env, key, reservation.spawnId, { agent, promptPreview: preview, sessionId });
|
|
24152
25488
|
if (!sessionId) {
|
|
24153
25489
|
return agentFail("spawn_failed", `the machine daemon did not confirm a session: ${String(spawnError)}`);
|
|
24154
25490
|
}
|
|
@@ -24160,6 +25496,7 @@ async function handoffToSibling(runtime, scope, env, handoff, agent, modelOption
|
|
|
24160
25496
|
depth: env.depth + 1,
|
|
24161
25497
|
spawned: spawned + 1,
|
|
24162
25498
|
maxSpawn: env.maxSpawn,
|
|
25499
|
+
lineageRecorded,
|
|
24163
25500
|
handoff: { mode: "summary", from }
|
|
24164
25501
|
});
|
|
24165
25502
|
}
|
|
@@ -24241,6 +25578,7 @@ function canControlSpawnedDescendant(records, parentSessionId, childSessionId) {
|
|
|
24241
25578
|
return resolveSubagentControlPermission(lineage, parentSessionId, childSessionId);
|
|
24242
25579
|
}
|
|
24243
25580
|
function ownershipFailure(runtime, env, target) {
|
|
25581
|
+
if (env.depth === 0) return null;
|
|
24244
25582
|
if (!env.sessionId) return agentFail("session_unknown", "This session did not receive APLUS_SESSION_ID.");
|
|
24245
25583
|
if (!runtime.readLineage) return agentFail("not_owned", "spawn lineage is unavailable");
|
|
24246
25584
|
try {
|
|
@@ -24281,6 +25619,143 @@ async function stopOwnedTarget(runtime, env, target) {
|
|
|
24281
25619
|
return agentFail("stop_rejected", error instanceof Error ? error.message : String(error));
|
|
24282
25620
|
}
|
|
24283
25621
|
}
|
|
25622
|
+
const LAST_AGENT_TEXT_PREVIEW_LENGTH = 240;
|
|
25623
|
+
function deriveAgentSessionState(snapshot, active) {
|
|
25624
|
+
if (!active) return "ended";
|
|
25625
|
+
if (awaitingUserInput(snapshot)) return "waiting-input";
|
|
25626
|
+
if (snapshot.thinking) return "responding";
|
|
25627
|
+
return "idle";
|
|
25628
|
+
}
|
|
25629
|
+
function turnOpenInWindow(contents) {
|
|
25630
|
+
let lastEnd = -1;
|
|
25631
|
+
let lastActivity = -1;
|
|
25632
|
+
contents.forEach((content, index) => {
|
|
25633
|
+
if (turnBoundary(content) === "end") lastEnd = index;
|
|
25634
|
+
else if (isTurnActivity(content)) lastActivity = index;
|
|
25635
|
+
});
|
|
25636
|
+
return lastActivity > lastEnd;
|
|
25637
|
+
}
|
|
25638
|
+
const TURN_ACTIVITY_EVENTS = /* @__PURE__ */ new Set(["turn-start", "text", "tool-call-start", "tool-call-progress", "tool-call-end"]);
|
|
25639
|
+
function isTurnActivity(content) {
|
|
25640
|
+
if (content.role === "user") return true;
|
|
25641
|
+
if (content.role !== "session") return false;
|
|
25642
|
+
return TURN_ACTIVITY_EVENTS.has(content.content.ev.t);
|
|
25643
|
+
}
|
|
25644
|
+
function lastAgentTextPreview(message, max = LAST_AGENT_TEXT_PREVIEW_LENGTH) {
|
|
25645
|
+
if (!message) return null;
|
|
25646
|
+
const text2 = message.content;
|
|
25647
|
+
return text2.length <= max ? text2 : `${text2.slice(0, max)}…`;
|
|
25648
|
+
}
|
|
25649
|
+
function pendingOf(snapshot) {
|
|
25650
|
+
return {
|
|
25651
|
+
askUserQuestion: Boolean(snapshot.pendingAskUserQuestion),
|
|
25652
|
+
exitPlanMode: Boolean(snapshot.pendingExitPlanMode),
|
|
25653
|
+
permissionRequests: snapshot.pendingPermissionRequestCount ?? 0
|
|
25654
|
+
};
|
|
25655
|
+
}
|
|
25656
|
+
const STATUS_CONCURRENCY = 4;
|
|
25657
|
+
async function sessionStatus(runtime, session) {
|
|
25658
|
+
let sequenced = [];
|
|
25659
|
+
let error = null;
|
|
25660
|
+
try {
|
|
25661
|
+
sequenced = await runtime.readSequencedMessages(session.id, STATE_WINDOW_MESSAGES);
|
|
25662
|
+
} catch (cause) {
|
|
25663
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
25664
|
+
}
|
|
25665
|
+
let snapshot = createWaitSnapshot();
|
|
25666
|
+
for (const { content } of sequenced) snapshot = foldMessageIntoSnapshot(snapshot, content);
|
|
25667
|
+
snapshot = foldAgentStateIntoSnapshot(snapshot, session.agentState ?? null);
|
|
25668
|
+
if (turnOpenInWindow(sequenced.map((entry) => entry.content))) snapshot = { ...snapshot, thinking: true };
|
|
25669
|
+
return {
|
|
25670
|
+
...sessionSummary(session),
|
|
25671
|
+
state: deriveAgentSessionState(snapshot, session.active),
|
|
25672
|
+
turnEndedAt: snapshot.lastResponseCompletedAt ?? 0,
|
|
25673
|
+
completion: completionSignalOf(snapshot),
|
|
25674
|
+
pending: pendingOf(snapshot),
|
|
25675
|
+
lastAgentText: lastAgentTextPreview(snapshot.finalAgentMessage),
|
|
25676
|
+
lastSeq: sequenced.reduce((max, entry) => Math.max(max, entry.seq), 0),
|
|
25677
|
+
...error !== null ? { error } : {}
|
|
25678
|
+
};
|
|
25679
|
+
}
|
|
25680
|
+
async function listSessionStatus(runtime, scope) {
|
|
25681
|
+
const sessions = sessionsInScope(await runtime.listSessions(), scope);
|
|
25682
|
+
const rows = [];
|
|
25683
|
+
for (let index = 0; index < sessions.length; index += STATUS_CONCURRENCY) {
|
|
25684
|
+
const batch = sessions.slice(index, index + STATUS_CONCURRENCY);
|
|
25685
|
+
rows.push(...await Promise.all(batch.map((session) => sessionStatus(runtime, session))));
|
|
25686
|
+
}
|
|
25687
|
+
return agentOk({ sessions: rows });
|
|
25688
|
+
}
|
|
25689
|
+
async function resolveAll(runtime, scope, sessionIds) {
|
|
25690
|
+
const sessions = await runtime.listSessions();
|
|
25691
|
+
const targets = [];
|
|
25692
|
+
for (const sessionId of sessionIds) {
|
|
25693
|
+
const session = sessions.find((candidate) => candidate.id === sessionId);
|
|
25694
|
+
if (!session) return agentFail("session_not_found", sessionId);
|
|
25695
|
+
if (!isInScope(session, scope)) return agentFail("out_of_scope", sessionId);
|
|
25696
|
+
targets.push({ session, scope });
|
|
25697
|
+
}
|
|
25698
|
+
return targets;
|
|
25699
|
+
}
|
|
25700
|
+
async function waitForAnyTarget(runtime, scope, sessionIds, until, timeoutMs) {
|
|
25701
|
+
const resolved = await resolveAll(runtime, scope, sessionIds);
|
|
25702
|
+
if (!Array.isArray(resolved)) return resolved;
|
|
25703
|
+
const since = runtime.now();
|
|
25704
|
+
const predicate = untilPredicate(until, since);
|
|
25705
|
+
const streams = [];
|
|
25706
|
+
for (const target of resolved) {
|
|
25707
|
+
try {
|
|
25708
|
+
streams.push(await runtime.openStream(target.session.id));
|
|
25709
|
+
} catch (error) {
|
|
25710
|
+
for (const stream2 of streams) stream2.close();
|
|
25711
|
+
throw error;
|
|
25712
|
+
}
|
|
25713
|
+
}
|
|
25714
|
+
const controllers = resolved.map(() => new AbortController());
|
|
25715
|
+
const pins = resolved.map((target, index) => startIdentityPin(runtime, target, controllers[index]));
|
|
25716
|
+
const outcomes = new Map(resolved.map((target) => [target.session.id, "pending"]));
|
|
25717
|
+
try {
|
|
25718
|
+
await new Promise((resolve) => {
|
|
25719
|
+
let settled = 0;
|
|
25720
|
+
resolved.forEach((target, index) => {
|
|
25721
|
+
void waitForSessionState({
|
|
25722
|
+
current: streams[index].current,
|
|
25723
|
+
subscribe: streams[index].subscribe,
|
|
25724
|
+
predicate,
|
|
25725
|
+
timeoutMs,
|
|
25726
|
+
signal: controllers[index].signal
|
|
25727
|
+
}).then((result) => {
|
|
25728
|
+
const id = target.session.id;
|
|
25729
|
+
if (result.outcome === "satisfied") outcomes.set(id, "satisfied");
|
|
25730
|
+
else if (result.outcome === "aborted") {
|
|
25731
|
+
if (pins[index].lost()) outcomes.set(id, "lost");
|
|
25732
|
+
} else outcomes.set(id, "timeout");
|
|
25733
|
+
settled += 1;
|
|
25734
|
+
if (result.outcome === "satisfied" || settled === resolved.length) resolve();
|
|
25735
|
+
});
|
|
25736
|
+
});
|
|
25737
|
+
});
|
|
25738
|
+
} finally {
|
|
25739
|
+
for (const controller of controllers) controller.abort();
|
|
25740
|
+
for (const pin of pins) pin.stop();
|
|
25741
|
+
for (const stream2 of streams) stream2.close();
|
|
25742
|
+
}
|
|
25743
|
+
const satisfied = resolved.filter((target, index) => outcomes.get(target.session.id) !== "lost" && predicate(streams[index].current())).map((target) => target.session.id);
|
|
25744
|
+
const lost = resolved.map((target) => target.session.id).filter((id) => outcomes.get(id) === "lost");
|
|
25745
|
+
const pending = resolved.map((target) => target.session.id).filter((id) => !satisfied.includes(id) && !lost.includes(id));
|
|
25746
|
+
if (satisfied.length > 0) {
|
|
25747
|
+
return agentOk({
|
|
25748
|
+
outcome: "satisfied",
|
|
25749
|
+
until,
|
|
25750
|
+
satisfied,
|
|
25751
|
+
pending,
|
|
25752
|
+
lost,
|
|
25753
|
+
sessions: resolved.filter((target) => satisfied.includes(target.session.id)).map((target) => sessionSummary(target.session))
|
|
25754
|
+
});
|
|
25755
|
+
}
|
|
25756
|
+
if (lost.length === resolved.length) return agentFail("session_not_running", lost.join(","));
|
|
25757
|
+
return agentFail("wait_timeout", `${until} not reached within ${timeoutMs}ms${lost.length ? ` (lost: ${lost.join(",")})` : ""}`);
|
|
25758
|
+
}
|
|
24284
25759
|
const CLI_CONFIGURABLE_AGENTS = [.../* @__PURE__ */ new Set([
|
|
24285
25760
|
...newSessionAgents.map(({ id }) => id),
|
|
24286
25761
|
"gemini"
|
|
@@ -24295,9 +25770,8 @@ function cliSpawnModelOptions() {
|
|
|
24295
25770
|
}));
|
|
24296
25771
|
}
|
|
24297
25772
|
const DEFAULT_READ_LIMIT = 20;
|
|
24298
|
-
const STATE_WINDOW_MESSAGES = 50;
|
|
24299
25773
|
function scopeOf(runtime) {
|
|
24300
|
-
const env = readAgentEnv(runtime.env);
|
|
25774
|
+
const env = readAgentEnv(runtime.env, runtime.machineBudget);
|
|
24301
25775
|
if (!env) return agentFail("not_agent_env", "This command only runs inside a Saycode-spawned session.");
|
|
24302
25776
|
const machineId = readMachineId(
|
|
24303
25777
|
{ homeDir: runtime.homeDir, env: runtime.env, credentialHome: runtime.credentialHome ?? null },
|
|
@@ -24306,10 +25780,7 @@ function scopeOf(runtime) {
|
|
|
24306
25780
|
if (!machineId) {
|
|
24307
25781
|
return agentFail("out_of_scope", "Could not determine this machine from any happy home settings.");
|
|
24308
25782
|
}
|
|
24309
|
-
return { scope: { root: env.
|
|
24310
|
-
}
|
|
24311
|
-
function isFailure(value2) {
|
|
24312
|
-
return typeof value2 === "object" && value2 !== null && "stdout" in value2;
|
|
25783
|
+
return { scope: { root: env.scope, machineId }, env };
|
|
24313
25784
|
}
|
|
24314
25785
|
async function resolveTarget(runtime, scope, sessionId) {
|
|
24315
25786
|
const sessions = await runtime.listSessions();
|
|
@@ -24318,13 +25789,13 @@ async function resolveTarget(runtime, scope, sessionId) {
|
|
|
24318
25789
|
if (!isInScope(session, scope)) return agentFail("out_of_scope", sessionId);
|
|
24319
25790
|
return { session, scope };
|
|
24320
25791
|
}
|
|
24321
|
-
async function readSession(runtime, target, last) {
|
|
24322
|
-
const
|
|
25792
|
+
async function readSession(runtime, target, last, since = 0) {
|
|
25793
|
+
const sequenced = await runtime.readSequencedMessages(target.session.id, Math.max(last, STATE_WINDOW_MESSAGES));
|
|
24323
25794
|
let snapshot = createWaitSnapshot();
|
|
24324
25795
|
const text2 = [];
|
|
24325
|
-
for (const message of
|
|
25796
|
+
for (const { seq, content: message } of sequenced) {
|
|
24326
25797
|
snapshot = foldMessageIntoSnapshot(snapshot, message);
|
|
24327
|
-
if (message.role !== "session") continue;
|
|
25798
|
+
if (seq <= since || message.role !== "session") continue;
|
|
24328
25799
|
const envelope = message.content;
|
|
24329
25800
|
if (envelope.ev.t !== "text") continue;
|
|
24330
25801
|
text2.push({
|
|
@@ -24340,6 +25811,8 @@ async function readSession(runtime, target, last) {
|
|
|
24340
25811
|
session: sessionSummary(target.session),
|
|
24341
25812
|
turnEndedAt: snapshot.lastResponseCompletedAt ?? 0,
|
|
24342
25813
|
completion: completionSignalOf(snapshot),
|
|
25814
|
+
// 0 when the window is empty: "start from the beginning", never a stale cursor.
|
|
25815
|
+
lastSeq: sequenced.reduce((max, entry) => Math.max(max, entry.seq), 0),
|
|
24343
25816
|
// `slice(-0)` is `slice(0)` — the whole array. `--last 0` is a reasonable
|
|
24344
25817
|
// way to ask for state without the transcript, and it must not answer with
|
|
24345
25818
|
// every message in the window.
|
|
@@ -24353,22 +25826,18 @@ async function spawnSibling(runtime, env, text2, agent, contextFiles = [], model
|
|
|
24353
25826
|
}
|
|
24354
25827
|
const context = resolveSpawnContextFiles({
|
|
24355
25828
|
paths: contextFiles,
|
|
24356
|
-
root: env.
|
|
25829
|
+
root: env.scope,
|
|
24357
25830
|
isReadableFile: runtime.isReadableFile
|
|
24358
25831
|
});
|
|
24359
25832
|
if (!context.ok) {
|
|
24360
|
-
return context.reason === "out_of_scope" ? agentFail("out_of_scope", `${context.path} is outside ${env.
|
|
24361
|
-
}
|
|
24362
|
-
const key = spawnBudgetKey(env);
|
|
24363
|
-
let spawned;
|
|
24364
|
-
try {
|
|
24365
|
-
spawned = runtime.countSpawns(key);
|
|
24366
|
-
} catch (error) {
|
|
24367
|
-
return agentFail("spawn_limit_exceeded", `spawn ledger is unreadable, refusing: ${String(error)}`);
|
|
25833
|
+
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`);
|
|
24368
25834
|
}
|
|
24369
|
-
const
|
|
24370
|
-
if (
|
|
25835
|
+
const budget = checkSpawnBudget(runtime, env);
|
|
25836
|
+
if (isAgentFailure(budget)) return budget;
|
|
25837
|
+
const { spawned, key } = budget;
|
|
24371
25838
|
const childId = runtime.newAgentId();
|
|
25839
|
+
const reservation = reserveSpawn(runtime, env, key, { agent, promptPreview: text2 }, spawned);
|
|
25840
|
+
if (isAgentFailure(reservation)) return reservation;
|
|
24372
25841
|
let sessionId = null;
|
|
24373
25842
|
let spawnError = null;
|
|
24374
25843
|
try {
|
|
@@ -24382,41 +25851,40 @@ async function spawnSibling(runtime, env, text2, agent, contextFiles = [], model
|
|
|
24382
25851
|
} catch (error) {
|
|
24383
25852
|
spawnError = error;
|
|
24384
25853
|
}
|
|
24385
|
-
|
|
24386
|
-
runtime.recordSpawn({
|
|
24387
|
-
ts: runtime.now(),
|
|
24388
|
-
agentKey: key,
|
|
24389
|
-
root: env.root,
|
|
24390
|
-
depth: env.depth,
|
|
24391
|
-
childSessionId: sessionId,
|
|
24392
|
-
...env.sessionId ? { parentSessionId: env.sessionId } : {},
|
|
24393
|
-
agent,
|
|
24394
|
-
promptPreview: text2,
|
|
24395
|
-
outcome: sessionId ? "spawned" : "failed"
|
|
24396
|
-
});
|
|
24397
|
-
} catch (error) {
|
|
24398
|
-
const created = sessionId ? `spawned ${sessionId} but ` : "";
|
|
24399
|
-
return agentFail("spawn_limit_exceeded", `${created}the ledger write failed: ${String(error)}`);
|
|
24400
|
-
}
|
|
25854
|
+
const lineageRecorded = resolveSpawn(runtime, env, key, reservation.spawnId, { agent, promptPreview: text2, sessionId });
|
|
24401
25855
|
if (!sessionId) {
|
|
24402
25856
|
return agentFail("spawn_failed", `the machine daemon did not confirm a session: ${String(spawnError)}`);
|
|
24403
25857
|
}
|
|
24404
25858
|
await fileSpawnedSessionUnderProject(runtime, sessionId, env.root);
|
|
24405
|
-
return agentOk({
|
|
25859
|
+
return agentOk({
|
|
25860
|
+
sessionId,
|
|
25861
|
+
agent,
|
|
25862
|
+
directory: env.root,
|
|
25863
|
+
depth: env.depth + 1,
|
|
25864
|
+
spawned: spawned + 1,
|
|
25865
|
+
maxSpawn: env.maxSpawn,
|
|
25866
|
+
// False means the child runs but this session's ownership link went
|
|
25867
|
+
// unwritten, so a spawned parent cannot steer or stop it.
|
|
25868
|
+
lineageRecorded
|
|
25869
|
+
});
|
|
24406
25870
|
}
|
|
24407
25871
|
async function runAgentCommand(runtime, args) {
|
|
24408
25872
|
const context = scopeOf(runtime);
|
|
24409
|
-
if (
|
|
25873
|
+
if (isAgentFailure(context)) return context;
|
|
24410
25874
|
const { scope, env } = context;
|
|
24411
25875
|
if (args.verb === "whoami") {
|
|
24412
25876
|
let spawned = null;
|
|
25877
|
+
let resetsAt = null;
|
|
24413
25878
|
try {
|
|
24414
|
-
|
|
25879
|
+
const usage = runtime.countSpawns(spawnBudgetKey(env), env.maxSpawn);
|
|
25880
|
+
spawned = usage.spawned;
|
|
25881
|
+
resetsAt = usage.resetsAt;
|
|
24415
25882
|
} catch {
|
|
24416
25883
|
spawned = null;
|
|
24417
25884
|
}
|
|
24418
25885
|
return agentOk({
|
|
24419
|
-
root:
|
|
25886
|
+
root: env.root,
|
|
25887
|
+
scope: env.scope,
|
|
24420
25888
|
machineId: scope.machineId,
|
|
24421
25889
|
// Which session this IS. Null when happy-cli did not supply it, and a
|
|
24422
25890
|
// handoff must then be told its source explicitly rather than guess.
|
|
@@ -24425,29 +25893,35 @@ async function runAgentCommand(runtime, args) {
|
|
|
24425
25893
|
maxSpawn: env.maxSpawn,
|
|
24426
25894
|
spawned,
|
|
24427
25895
|
spawnsRemaining: spawned === null ? null : Math.max(0, env.maxSpawn - spawned),
|
|
25896
|
+
// The budget is a rolling window, so "full" is a temporary state with a
|
|
25897
|
+
// known end — an agent that is told only "0 left" would give up for good.
|
|
25898
|
+
budgetResetsAt: resetsAt,
|
|
24428
25899
|
canSpawn: env.depth < AGENT_MAX_DEPTH && spawned !== null && spawned < env.maxSpawn,
|
|
24429
25900
|
spawnModelOptions: cliSpawnModelOptions()
|
|
24430
25901
|
});
|
|
24431
25902
|
}
|
|
24432
25903
|
if (args.verb === "ls") {
|
|
24433
|
-
|
|
24434
|
-
return agentOk({ sessions:
|
|
25904
|
+
if (args.status) return listSessionStatus(runtime, scope);
|
|
25905
|
+
return agentOk({ sessions: sessionsInScope(await runtime.listSessions(), scope).map(sessionSummary) });
|
|
24435
25906
|
}
|
|
24436
25907
|
if (args.verb === "spawn") {
|
|
24437
25908
|
const agent = args.agent ?? DEFAULT_SPAWN_AGENT;
|
|
24438
25909
|
const modelOptions = args.model !== void 0 || args.effort !== void 0 ? await validateSpawnModelOptions(agent, args.model, args.effort) : {};
|
|
24439
|
-
if (
|
|
25910
|
+
if (isAgentFailure(modelOptions)) return modelOptions;
|
|
24440
25911
|
if (args.handoff) {
|
|
24441
25912
|
return handoffToSibling(runtime, scope, env, args.handoff, agent, modelOptions);
|
|
24442
25913
|
}
|
|
24443
25914
|
return spawnSibling(runtime, env, args.text ?? "", agent, args.files ?? [], modelOptions);
|
|
24444
25915
|
}
|
|
25916
|
+
if (args.verb === "wait" && args.sessionIds) {
|
|
25917
|
+
return waitForAnyTarget(runtime, scope, args.sessionIds, args.until ?? "turn-end", clampWaitTimeout(args.timeoutMs));
|
|
25918
|
+
}
|
|
24445
25919
|
if (!args.sessionId) return agentFail("session_not_found", "no session id given");
|
|
24446
25920
|
const target = await resolveTarget(runtime, scope, args.sessionId);
|
|
24447
|
-
if (
|
|
25921
|
+
if (isAgentFailure(target)) return target;
|
|
24448
25922
|
switch (args.verb) {
|
|
24449
25923
|
case "read":
|
|
24450
|
-
return readSession(runtime, target, args.last ?? DEFAULT_READ_LIMIT);
|
|
25924
|
+
return readSession(runtime, target, args.last ?? DEFAULT_READ_LIMIT, args.since);
|
|
24451
25925
|
case "wait":
|
|
24452
25926
|
return waitForTarget(runtime, target, args.until ?? "turn-end", clampWaitTimeout(args.timeoutMs));
|
|
24453
25927
|
case "prompt":
|
|
@@ -24479,7 +25953,7 @@ async function validateSpawnModelOptions(agent, model, effort) {
|
|
|
24479
25953
|
function parseSpawnRecord(value2) {
|
|
24480
25954
|
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
|
|
24481
25955
|
const record2 = value2;
|
|
24482
|
-
return typeof record2.ts === "number" && Number.isFinite(record2.ts) && typeof record2.agentKey === "string" && record2.agentKey.length > 0 && typeof record2.root === "string" && record2.root.length > 0 && typeof record2.depth === "number" && Number.isInteger(record2.depth) && record2.depth >= 0 && (record2.childSessionId === null || typeof record2.childSessionId === "string" && record2.childSessionId.length > 0) && (record2.parentSessionId === void 0 || typeof record2.parentSessionId === "string" && record2.parentSessionId.length > 0) && typeof record2.agent === "string" && record2.agent.length > 0 && typeof record2.promptPreview === "string" && (record2.outcome === "spawned" || record2.outcome === "failed") ? record2 : null;
|
|
25956
|
+
return typeof record2.ts === "number" && Number.isFinite(record2.ts) && typeof record2.agentKey === "string" && record2.agentKey.length > 0 && typeof record2.root === "string" && record2.root.length > 0 && typeof record2.depth === "number" && Number.isInteger(record2.depth) && record2.depth >= 0 && (record2.childSessionId === null || typeof record2.childSessionId === "string" && record2.childSessionId.length > 0) && (record2.parentSessionId === void 0 || typeof record2.parentSessionId === "string" && record2.parentSessionId.length > 0) && (record2.spawnId === void 0 || typeof record2.spawnId === "string" && record2.spawnId.length > 0) && typeof record2.agent === "string" && record2.agent.length > 0 && typeof record2.promptPreview === "string" && (record2.outcome === "spawned" || record2.outcome === "failed") ? record2 : null;
|
|
24483
25957
|
}
|
|
24484
25958
|
const LEDGER_FILE_MODE = 384;
|
|
24485
25959
|
const PREVIEW_MAX_LENGTH = 200;
|
|
@@ -24520,7 +25994,8 @@ function recordIdentity(record2) {
|
|
|
24520
25994
|
record2.parentSessionId ?? null,
|
|
24521
25995
|
record2.agent,
|
|
24522
25996
|
record2.promptPreview,
|
|
24523
|
-
record2.outcome
|
|
25997
|
+
record2.outcome,
|
|
25998
|
+
record2.spawnId ?? null
|
|
24524
25999
|
]);
|
|
24525
26000
|
}
|
|
24526
26001
|
function readSpawnRecords(homeDir, rootDir) {
|
|
@@ -24536,14 +26011,35 @@ function readSpawnRecords(homeDir, rootDir) {
|
|
|
24536
26011
|
return true;
|
|
24537
26012
|
});
|
|
24538
26013
|
}
|
|
24539
|
-
function
|
|
24540
|
-
|
|
26014
|
+
function spawnBudgetUsage(homeDir, rootDir, agentKey, now, maxSpawn) {
|
|
26015
|
+
const mine = readSpawnRecords(homeDir, rootDir).filter((record2) => record2.agentKey === agentKey);
|
|
26016
|
+
return {
|
|
26017
|
+
spawned: chargedSpawnCount(mine, now),
|
|
26018
|
+
resetsAt: spawnBudgetResetsAt(mine, now, maxSpawn)
|
|
26019
|
+
};
|
|
26020
|
+
}
|
|
26021
|
+
function endsWithNewline(path) {
|
|
26022
|
+
let fd;
|
|
26023
|
+
try {
|
|
26024
|
+
fd = openSync(path, "r");
|
|
26025
|
+
const size = fstatSync(fd).size;
|
|
26026
|
+
if (size === 0) return true;
|
|
26027
|
+
const tail = Buffer.alloc(1);
|
|
26028
|
+
readSync(fd, tail, 0, 1, size - 1);
|
|
26029
|
+
return tail[0] === 10;
|
|
26030
|
+
} catch (error) {
|
|
26031
|
+
if (error.code === "ENOENT") return true;
|
|
26032
|
+
throw error;
|
|
26033
|
+
} finally {
|
|
26034
|
+
if (fd !== void 0) closeSync(fd);
|
|
26035
|
+
}
|
|
24541
26036
|
}
|
|
24542
26037
|
function appendSpawnRecord(rootDir, record2) {
|
|
24543
26038
|
const path = agentLineagePath(rootDir);
|
|
24544
26039
|
mkdirSync(join(rootDir, ".aplus"), { recursive: true, mode: 448 });
|
|
24545
26040
|
const entry = { ...record2, promptPreview: record2.promptPreview.slice(0, PREVIEW_MAX_LENGTH) };
|
|
24546
|
-
|
|
26041
|
+
const separator = endsWithNewline(path) ? "" : "\n";
|
|
26042
|
+
appendFileSync(path, `${separator}${JSON.stringify(entry)}
|
|
24547
26043
|
`, { mode: LEDGER_FILE_MODE });
|
|
24548
26044
|
}
|
|
24549
26045
|
function normalize(raw) {
|
|
@@ -24688,7 +26184,7 @@ async function recentContents(context, transport, sessionId, limit) {
|
|
|
24688
26184
|
const ciphertext = unwrapEncryptedField(message.content);
|
|
24689
26185
|
if (!ciphertext) continue;
|
|
24690
26186
|
const content = decodeContent(transport, ciphertext);
|
|
24691
|
-
if (content) contents.push(content);
|
|
26187
|
+
if (content) contents.push({ seq: message.seq, content });
|
|
24692
26188
|
}
|
|
24693
26189
|
return contents;
|
|
24694
26190
|
}
|
|
@@ -24721,7 +26217,7 @@ async function openStream(context, transport, sessionId) {
|
|
|
24721
26217
|
else apply(update);
|
|
24722
26218
|
});
|
|
24723
26219
|
try {
|
|
24724
|
-
for (const content of await recentContents(context, transport, sessionId, STREAM_HISTORY_LIMIT)) {
|
|
26220
|
+
for (const { content } of await recentContents(context, transport, sessionId, STREAM_HISTORY_LIMIT)) {
|
|
24725
26221
|
snapshot = foldMessageIntoSnapshot(snapshot, content);
|
|
24726
26222
|
}
|
|
24727
26223
|
} catch (error) {
|
|
@@ -24745,17 +26241,21 @@ async function openStream(context, transport, sessionId) {
|
|
|
24745
26241
|
}
|
|
24746
26242
|
function createAgentRuntime(context, transport, env = process.env) {
|
|
24747
26243
|
const keys = [decodeBase64(context.syncCredential.secret)];
|
|
24748
|
-
const
|
|
26244
|
+
const machineBudget = machineSpawnBudget({ cpuCount: cpus().length, totalMemoryBytes: totalmem() });
|
|
26245
|
+
const homeDir = context.homeDir, lineageRoot = readAgentEnv(env, machineBudget)?.scope ?? context.homeDir;
|
|
24749
26246
|
const credentialHome = context.credential.source === "saycode" ? null : dirname(context.credential.path);
|
|
26247
|
+
const now = () => Date.now();
|
|
24750
26248
|
return {
|
|
24751
26249
|
env,
|
|
24752
26250
|
homeDir,
|
|
24753
26251
|
credentialHome,
|
|
24754
|
-
|
|
26252
|
+
machineBudget,
|
|
26253
|
+
now,
|
|
24755
26254
|
readFile: readFileOrNull,
|
|
24756
26255
|
isReadableFile: isReadableFileSync,
|
|
24757
26256
|
listSessions: async () => normalizeSessionRows(await context.rest.listSessions(), keys),
|
|
24758
|
-
readMessages: (sessionId, limit) => recentContents(context, transport, sessionId, limit),
|
|
26257
|
+
readMessages: async (sessionId, limit) => (await recentContents(context, transport, sessionId, limit)).map((entry) => entry.content),
|
|
26258
|
+
readSequencedMessages: (sessionId, limit) => recentContents(context, transport, sessionId, limit),
|
|
24759
26259
|
openStream: (sessionId) => openStream(context, transport, sessionId),
|
|
24760
26260
|
sendPrompt: async (sessionId, text2) => createCliSessionConnection({
|
|
24761
26261
|
rest: context.rest,
|
|
@@ -24786,7 +26286,7 @@ function createAgentRuntime(context, transport, env = process.env) {
|
|
|
24786
26286
|
return { sessionId: result.sessionId };
|
|
24787
26287
|
},
|
|
24788
26288
|
newAgentId: () => randomBytes(8).toString("hex"),
|
|
24789
|
-
countSpawns: (agentKey) =>
|
|
26289
|
+
countSpawns: (agentKey, maxSpawn) => spawnBudgetUsage(homeDir, lineageRoot, agentKey, now(), maxSpawn),
|
|
24790
26290
|
recordSpawn: (record2) => appendSpawnRecord(lineageRoot, record2),
|
|
24791
26291
|
readLineage: () => readSpawnRecords(homeDir, lineageRoot),
|
|
24792
26292
|
steerSession: (sessionId, text2) => steerAgentSession(transport, sessionId, text2),
|