@buzzni/saycode-cli 0.3.1 → 0.3.2
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 +2 -1
- package/index.mjs +364 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,8 @@ happy agent stop <id> # 사용자가 명시한 경우에만 현재 응
|
|
|
124
124
|
|
|
125
125
|
`steer` 성공의 `accepted:true`는 세션 RPC가 요청을 받았다는 뜻이며 실제 transcript 전달 완료를
|
|
126
126
|
뜻하지 않습니다(`delivered:false`). 끝난 턴의 후속 작업에는 `prompt`를 사용합니다. `steer`와
|
|
127
|
-
`stop`은
|
|
127
|
+
`stop`은 원장에 기록된 최대 2단계 자식만 허용하며, `stop`은 보관·삭제를 하지 않습니다. 이 원장 검사는
|
|
128
|
+
같은 OS 사용자 아래의 오조작을 줄이는 advisory 가드레일이며 보안 경계나 서버 측 권한 증명은 아닙니다.
|
|
128
129
|
같은 worktree에는 동시에 둘 이상의 쓰기 자식을 만들지 말고 격리가 필요하면 Desktop의 관리형
|
|
129
130
|
worktree 병렬 실행을 사용하세요.
|
|
130
131
|
|
package/index.mjs
CHANGED
|
@@ -40,20 +40,20 @@ function _mergeNamespaces(n, m) {
|
|
|
40
40
|
}
|
|
41
41
|
return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: "Module" }));
|
|
42
42
|
}
|
|
43
|
-
const version = "0.3.
|
|
43
|
+
const version = "0.3.2";
|
|
44
44
|
const manifest = {
|
|
45
45
|
version
|
|
46
46
|
};
|
|
47
47
|
const AGENT_CONTROL_VERBS = ["steer", "stop"];
|
|
48
|
-
function parseAgentControlArgs(verb, target, rest) {
|
|
48
|
+
function parseAgentControlArgs(verb, target, rest, hasPositionalTarget) {
|
|
49
49
|
if (verb === "steer") {
|
|
50
50
|
if (!target) return { command: "error", message: "agent steer needs a session id (see `saycode agent ls`)" };
|
|
51
|
-
const text2 = rest.slice(1).join(" ").trim();
|
|
51
|
+
const text2 = rest.slice(hasPositionalTarget ? 1 : 0).join(" ").trim();
|
|
52
52
|
return text2 ? { command: "agent", verb: "steer", sessionId: target, text: text2 } : { command: "error", message: "agent steer needs the text to send" };
|
|
53
53
|
}
|
|
54
54
|
if (verb === "stop") {
|
|
55
55
|
if (!target) return { command: "error", message: "agent stop needs a session id (see `saycode agent ls`)" };
|
|
56
|
-
return rest.length === 1 ? { command: "agent", verb: "stop", sessionId: target } : { command: "error", message: "agent stop takes exactly one session id" };
|
|
56
|
+
return rest.length === (hasPositionalTarget ? 1 : 0) ? { command: "agent", verb: "stop", sessionId: target } : { command: "error", message: "agent stop takes exactly one session id" };
|
|
57
57
|
}
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
@@ -67,8 +67,9 @@ function parseAgentArgs(flags, positionals) {
|
|
|
67
67
|
return fail(`agent needs one of: ${AGENT_VERBS.join(", ")}`);
|
|
68
68
|
}
|
|
69
69
|
const timeout = flags.timeoutMs !== void 0 ? { timeoutMs: flags.timeoutMs } : {};
|
|
70
|
-
const
|
|
71
|
-
const
|
|
70
|
+
const hasPositionalTarget = flags.sessionId === void 0 && rest[0] !== void 0;
|
|
71
|
+
const target = flags.sessionId ?? rest[0];
|
|
72
|
+
const control = parseAgentControlArgs(verb, target, rest, hasPositionalTarget);
|
|
72
73
|
if (control) return control;
|
|
73
74
|
switch (verb) {
|
|
74
75
|
case "whoami":
|
|
@@ -86,7 +87,7 @@ function parseAgentArgs(flags, positionals) {
|
|
|
86
87
|
}
|
|
87
88
|
case "prompt": {
|
|
88
89
|
if (!target) return fail("agent prompt needs a session id (see `saycode agent ls`)");
|
|
89
|
-
const text2 = rest.slice(1).join(" ").trim();
|
|
90
|
+
const text2 = rest.slice(hasPositionalTarget ? 1 : 0).join(" ").trim();
|
|
90
91
|
if (!text2) return fail("agent prompt needs the text to send");
|
|
91
92
|
return {
|
|
92
93
|
command: "agent",
|
|
@@ -118,7 +119,9 @@ function parseAgentArgs(flags, positionals) {
|
|
|
118
119
|
command: "agent",
|
|
119
120
|
verb: "spawn",
|
|
120
121
|
handoff: { mode: "summary", ...from ? { from } : {} },
|
|
121
|
-
...flags.agent ? { agent: flags.agent } : {}
|
|
122
|
+
...flags.agent ? { agent: flags.agent } : {},
|
|
123
|
+
...flags.model !== void 0 ? { model: flags.model } : {},
|
|
124
|
+
...flags.effort !== void 0 ? { effort: flags.effort } : {}
|
|
122
125
|
};
|
|
123
126
|
}
|
|
124
127
|
const text2 = flags.prompt?.trim();
|
|
@@ -128,6 +131,8 @@ function parseAgentArgs(flags, positionals) {
|
|
|
128
131
|
verb: "spawn",
|
|
129
132
|
text: text2,
|
|
130
133
|
...flags.agent ? { agent: flags.agent } : {},
|
|
134
|
+
...flags.model !== void 0 ? { model: flags.model } : {},
|
|
135
|
+
...flags.effort !== void 0 ? { effort: flags.effort } : {},
|
|
131
136
|
...files.length > 0 ? { files } : {}
|
|
132
137
|
};
|
|
133
138
|
}
|
|
@@ -296,7 +301,8 @@ const completionInboxKo = {
|
|
|
296
301
|
"completionInbox.emptyReadDescription": "확인한 알림이 여기에 표시됩니다.",
|
|
297
302
|
"completionInbox.unreadList": "미확인 알림 목록",
|
|
298
303
|
"completionInbox.readList": "확인한 알림 목록",
|
|
299
|
-
"completionInbox.projectFallback": "프로젝트"
|
|
304
|
+
"completionInbox.projectFallback": "프로젝트",
|
|
305
|
+
"completionInbox.platformNoticeFallbackTitle": "알림"
|
|
300
306
|
};
|
|
301
307
|
const agentBoardKo = {
|
|
302
308
|
"agentBoard.preparingResponse": "응답을 준비하는 중…",
|
|
@@ -1323,6 +1329,9 @@ const subagentKo = {
|
|
|
1323
1329
|
"subagent.error.inFlight": "이전 제어 요청을 처리하는 중입니다.",
|
|
1324
1330
|
"subagent.error.rejected": "요청이 거부됐습니다: {detail}"
|
|
1325
1331
|
};
|
|
1332
|
+
const reviewNavigationKo = {
|
|
1333
|
+
"review.action.openSourceSession": "원본 대화로 이동: {title}"
|
|
1334
|
+
};
|
|
1326
1335
|
const aiCommitPrKo = {
|
|
1327
1336
|
"aiCommitPr.stagedCount": "스테이지된 파일 {count}개",
|
|
1328
1337
|
"aiCommitPr.noStaged": "스테이지된 변경이 없습니다.",
|
|
@@ -1363,7 +1372,39 @@ const aiCommitPrKo = {
|
|
|
1363
1372
|
"aiCommitPr.pr.create": "Pull Request 만들기",
|
|
1364
1373
|
"aiCommitPr.pr.creating": "만드는 중…"
|
|
1365
1374
|
};
|
|
1366
|
-
const
|
|
1375
|
+
const projectHandoffKo = {
|
|
1376
|
+
"projectHandoff.loading": "핸드오프를 확인하는 중…",
|
|
1377
|
+
"projectHandoff.notFound": "핸드오프를 찾을 수 없습니다.",
|
|
1378
|
+
"projectHandoff.autoApproving": "자동 승인 중…",
|
|
1379
|
+
"projectHandoff.autoApproveFailed": "자동 승인에 실패했습니다.",
|
|
1380
|
+
"projectHandoff.status.queued": "대기 중",
|
|
1381
|
+
"projectHandoff.status.running": "진행 중",
|
|
1382
|
+
"projectHandoff.status.completed": "완료됨",
|
|
1383
|
+
"projectHandoff.status.failed": "실패함",
|
|
1384
|
+
"projectHandoff.status.cancelled": "거절됨",
|
|
1385
|
+
"projectHandoff.status.blocked": "막힘",
|
|
1386
|
+
"projectHandoff.indicatorLabel": "넘긴 작업 {count}건 진행 중"
|
|
1387
|
+
};
|
|
1388
|
+
const projectLinksKo = {
|
|
1389
|
+
"projectLinks.empty": "등록된 관련 프로젝트가 없습니다.",
|
|
1390
|
+
"projectLinks.loading": "불러오는 중…",
|
|
1391
|
+
"projectLinks.loadFailed": "관련 프로젝트를 불러오지 못했습니다.",
|
|
1392
|
+
"projectLinks.unknownProject": "알 수 없는 프로젝트",
|
|
1393
|
+
"projectLinks.removeAria": "{name} 관련 등록 해제",
|
|
1394
|
+
"projectLinks.removeFailed": "관련 등록을 해제하지 못했습니다.",
|
|
1395
|
+
"projectLinks.selectProject": "프로젝트 선택",
|
|
1396
|
+
"projectLinks.selectProjectAria": "관련 프로젝트로 등록할 프로젝트",
|
|
1397
|
+
"projectLinks.rolePlaceholder": "역할 (예: 웹, 백엔드, 앱)",
|
|
1398
|
+
"projectLinks.add": "등록",
|
|
1399
|
+
"projectLinks.adding": "등록 중…",
|
|
1400
|
+
"projectLinks.addFailed": "관련 프로젝트를 등록하지 못했습니다.",
|
|
1401
|
+
"projectLinks.noCandidates": "등록할 수 있는 다른 프로젝트가 없습니다.",
|
|
1402
|
+
"projectLinks.unavailableInDemo": "데모 모드에서는 관련 프로젝트를 관리할 수 없습니다.",
|
|
1403
|
+
"projectLinks.mcpToggleLabel": "AI가 이 프로젝트에서 작업을 넘길 수 있게 허용",
|
|
1404
|
+
"projectLinks.mcpToggleHint": "켜면 AI가 관련 프로젝트로 작업을 제안했을 때 확인 없이 바로 진행됩니다.",
|
|
1405
|
+
"projectLinks.mcpToggleFailed": "설정을 바꾸지 못했습니다."
|
|
1406
|
+
};
|
|
1407
|
+
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, ...terminalFontKo, ...globalPauseKo, ...subagentKo, ...reviewNavigationKo, ...projectLinksKo, ...projectHandoffKo };
|
|
1367
1408
|
const onboardingKo = {
|
|
1368
1409
|
"onboarding.dialogAria": "시작 설정",
|
|
1369
1410
|
"onboarding.eyebrow": "처음 시작하기",
|
|
@@ -2306,6 +2347,7 @@ const ko = {
|
|
|
2306
2347
|
"projectSettings.tabRepository": "코드 저장소",
|
|
2307
2348
|
"projectSettings.tabDeploy": "배포 저장소",
|
|
2308
2349
|
"projectSettings.tabCollaboration": "협업",
|
|
2350
|
+
"projectSettings.tabLinks": "관련 프로젝트",
|
|
2309
2351
|
"projectSettings.tabMachine": "머신 및 경로",
|
|
2310
2352
|
"projectSettings.tabRun": "실행",
|
|
2311
2353
|
"projectSettings.tabMcp": "MCP 서버/스킬",
|
|
@@ -2935,6 +2977,7 @@ const ko = {
|
|
|
2935
2977
|
"worktreeOp.checkingStatus": "worktree 상태 확인 중...",
|
|
2936
2978
|
"worktreeOp.forceDeleteUnpushed": "worktree에 푸시되지 않은 커밋 {count}개가 있습니다. 강제로 삭제할까요? 커밋이 사라집니다.",
|
|
2937
2979
|
"worktreeOp.forceDeleteDirty": "worktree에 정리되지 않은 변경이 있습니다. 강제로 삭제할까요? 변경 사항이 사라집니다.",
|
|
2980
|
+
"worktreeOp.trackedChangesBlocked": "이 worktree에 커밋되지 않은 tracked 변경이 있습니다. 변경을 커밋하거나 stash하거나 명시적으로 버린 뒤 다시 시도하세요.",
|
|
2938
2981
|
"worktreeOp.removeCancelled": "정리되지 않은 변경/커밋이 있어 삭제를 취소했습니다.",
|
|
2939
2982
|
"worktreeOp.removed": "worktree를 삭제했습니다.",
|
|
2940
2983
|
"project.sharedBy": " · 공유: {owner}",
|
|
@@ -3300,6 +3343,7 @@ const ko = {
|
|
|
3300
3343
|
"saycodePromptPreference.eyebrow": "에이전트 지시 선택",
|
|
3301
3344
|
"saycodePromptPreference.title": "Saycode 기본 지시를 사용할까요?",
|
|
3302
3345
|
"saycodePromptPreference.body": "내가 만든 Claude/Codex 스킬과 하네스를 그대로 쓰려면 꺼진 상태를 유지하세요. 나중에 환경설정에서 언제든 바꿀 수 있습니다.",
|
|
3346
|
+
"saycodePromptPreference.defaultOnNotice": "자식 Agent 호출, 내부 작업 위임, 결과물 인라인 미리보기는 기본으로 계속 켜집니다. 이 기능도 끄려면 환경설정 → 시스템 프롬프트에서 각각 해제하세요.",
|
|
3303
3347
|
"saycodePromptPreference.keepOff": "꺼진 상태 유지",
|
|
3304
3348
|
"saycodePromptPreference.enable": "Saycode 지시 켜기",
|
|
3305
3349
|
"saycodePromptPreference.saving": "저장 중…",
|
|
@@ -3308,9 +3352,11 @@ const ko = {
|
|
|
3308
3352
|
"saycodePromptPreference.settingsEnable": "Saycode가 제공하는 기본 행동 지시 사용",
|
|
3309
3353
|
"saycodePromptPreference.settingsHelp": "사용자 시스템 프롬프트, 프로젝트 지침, 설치한 스킬과 하네스는 이 설정과 관계없이 유지됩니다.",
|
|
3310
3354
|
"saycodePromptPreference.blocksTitle": "개별 지시 선택",
|
|
3311
|
-
"saycodePromptPreference.blocksHelp": "
|
|
3355
|
+
"saycodePromptPreference.blocksHelp": "자식 Agent 호출, 내부 작업 위임, 결과물 인라인 미리보기는 기본으로 켜지며, 나머지 항목은 위의 전체 설정을 따릅니다.",
|
|
3312
3356
|
"saycodePromptPreference.alwaysOnNotice": "채팅 제목 생성은 항상 켜져 있으며 끌 수 없습니다. 채팅 목록에서 대화를 다시 찾으려면 필요합니다.",
|
|
3313
|
-
"saycodePromptPreference.block.
|
|
3357
|
+
"saycodePromptPreference.block.agentOrchestration.label": "자식 Agent 호출",
|
|
3358
|
+
"saycodePromptPreference.block.agentOrchestration.description": "자연어로 요청한 작업을 독립된 자식 Agent 세션에 맡기고 결과를 회수하도록 지시합니다.",
|
|
3359
|
+
"saycodePromptPreference.block.workerDelegation.label": "내부 작업 위임",
|
|
3314
3360
|
"saycodePromptPreference.block.workerDelegation.description": "기계적인 작업을 저렴한 worker 에이전트에 넘기도록 지시합니다.",
|
|
3315
3361
|
"saycodePromptPreference.block.axBase.label": "기획부터 시작 워크플로우",
|
|
3316
3362
|
"saycodePromptPreference.block.axBase.description": "AX 워크플로우의 기본 지시를 사용합니다.",
|
|
@@ -4450,7 +4496,7 @@ Options
|
|
|
4450
4496
|
-p, --project <id|name> Project id prefix or name
|
|
4451
4497
|
-d, --directory <path> Working directory on that machine
|
|
4452
4498
|
-s, --session <id> Session id (see \`saycode ls\`)
|
|
4453
|
-
--agent <name> claude (default), codex, gemini, openclaw, opencode
|
|
4499
|
+
--agent <name> claude (default), codex, gemini, grok, openclaw, opencode
|
|
4454
4500
|
--model <name> Model for new-session messages (Default if omitted)
|
|
4455
4501
|
--effort <level> Model effort (validated against agent + model)
|
|
4456
4502
|
--worktree Create an isolated worktree for a project chat
|
|
@@ -4473,7 +4519,8 @@ Agent commands (machine-readable JSON on stdout, exit 3 on failure)
|
|
|
4473
4519
|
agent prompt <session> <text> [--wait] Send a prompt, optionally wait for it
|
|
4474
4520
|
agent steer <session> <text> Redirect an owned child's active response
|
|
4475
4521
|
agent stop <session> Stop an owned child's active response
|
|
4476
|
-
agent spawn --prompt <text> [--agent n]
|
|
4522
|
+
agent spawn --prompt <text> [--agent n] [--model n] [--effort n]
|
|
4523
|
+
Start a sibling with an explicit execution choice
|
|
4477
4524
|
agent spawn --prompt <text> --file <path> ...with context files it reads first
|
|
4478
4525
|
Only works inside a session Saycode started (SAYCODE_AGENT_ENV).
|
|
4479
4526
|
|
|
@@ -6872,6 +6919,58 @@ function buildUserTextMessageContent(input) {
|
|
|
6872
6919
|
}
|
|
6873
6920
|
};
|
|
6874
6921
|
}
|
|
6922
|
+
function sanitizedString(value2) {
|
|
6923
|
+
return typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
6924
|
+
}
|
|
6925
|
+
function sanitizedCompanyId(value2) {
|
|
6926
|
+
if (typeof value2 === "string") return value2.trim() ? value2.trim() : null;
|
|
6927
|
+
if (value2 === null) return null;
|
|
6928
|
+
return void 0;
|
|
6929
|
+
}
|
|
6930
|
+
function buildStoredNotice(sessionId, value2, receivedAt) {
|
|
6931
|
+
const companyId = sanitizedCompanyId(value2.companyId);
|
|
6932
|
+
return {
|
|
6933
|
+
sessionId,
|
|
6934
|
+
projectId: sanitizedString(value2.projectId),
|
|
6935
|
+
...sanitizedString(value2.personalChatId) ? { personalChatId: sanitizedString(value2.personalChatId) } : {},
|
|
6936
|
+
projectName: sanitizedString(value2.projectName),
|
|
6937
|
+
sessionTitle: sanitizedString(value2.sessionTitle),
|
|
6938
|
+
status: value2.status === "completed" || value2.status === "failed" || value2.status === "cancelled" ? value2.status : "completed",
|
|
6939
|
+
receivedAt,
|
|
6940
|
+
...companyId !== void 0 ? { companyId } : {},
|
|
6941
|
+
// Only non-chat kinds are stored; absence means chat so legacy snapshots round-trip.
|
|
6942
|
+
...value2.kind === "deploy" || value2.kind === "automation" || value2.kind === "platform_notice" ? { kind: value2.kind } : {},
|
|
6943
|
+
// title/body are platform_notice-only (server-authoritative admin message). Other
|
|
6944
|
+
// kinds never legitimately carry them, so they are dropped rather than stored blank.
|
|
6945
|
+
...value2.kind === "platform_notice" ? { title: sanitizedString(value2.title), body: sanitizedString(value2.body) } : {}
|
|
6946
|
+
};
|
|
6947
|
+
}
|
|
6948
|
+
function parseServerCompletionNotifications(payload) {
|
|
6949
|
+
if (!payload || typeof payload !== "object") return [];
|
|
6950
|
+
const { notifications } = payload;
|
|
6951
|
+
if (!Array.isArray(notifications)) return [];
|
|
6952
|
+
const result = [];
|
|
6953
|
+
for (const raw of notifications) {
|
|
6954
|
+
if (!raw || typeof raw !== "object") continue;
|
|
6955
|
+
const value2 = raw;
|
|
6956
|
+
if (value2.kind !== "platform_notice") continue;
|
|
6957
|
+
const sessionId = sanitizedString(value2.sessionId);
|
|
6958
|
+
if (!sessionId) continue;
|
|
6959
|
+
result.push({
|
|
6960
|
+
...buildStoredNotice(
|
|
6961
|
+
sessionId,
|
|
6962
|
+
value2,
|
|
6963
|
+
typeof value2.receivedAt === "number" && Number.isFinite(value2.receivedAt) ? value2.receivedAt : 0
|
|
6964
|
+
),
|
|
6965
|
+
// 서버 목록은 읽은 행도 함께 돌려주고(`listCompletionNotifications` 는 unread 로
|
|
6966
|
+
// 필터하지 않는다) 읽음 여부를 `unread` 에 담는다. `buildStoredNotice` 는 이 필드를
|
|
6967
|
+
// 모르므로 여기서 직접 얹지 않으면, 조회할 때마다 `recordCompletionNotice` 의
|
|
6968
|
+
// 기본값(미확인)으로 되살아난다.
|
|
6969
|
+
...typeof value2.unread === "boolean" ? { unread: value2.unread } : {}
|
|
6970
|
+
});
|
|
6971
|
+
}
|
|
6972
|
+
return result;
|
|
6973
|
+
}
|
|
6875
6974
|
const CONNECTOR_PROVIDER_IDS = [
|
|
6876
6975
|
"notion",
|
|
6877
6976
|
"slack",
|
|
@@ -16001,6 +16100,31 @@ function coercePromptSuggestion(value2) {
|
|
|
16001
16100
|
if (!text2 || !provider2 || updatedAt === void 0) return void 0;
|
|
16002
16101
|
return { text: text2, provider: provider2, updatedAt };
|
|
16003
16102
|
}
|
|
16103
|
+
const MCP_RUNTIME_STATUSES = /* @__PURE__ */ new Set([
|
|
16104
|
+
"connected",
|
|
16105
|
+
"reconnecting",
|
|
16106
|
+
"failed",
|
|
16107
|
+
"needs-auth",
|
|
16108
|
+
"config-fetch-failed",
|
|
16109
|
+
"mcp-config-missing",
|
|
16110
|
+
"connector-config-missing",
|
|
16111
|
+
"connector-runtime-failed",
|
|
16112
|
+
"connector-needs-auth"
|
|
16113
|
+
]);
|
|
16114
|
+
function coerceMcpServerStatuses(value2) {
|
|
16115
|
+
if (!Array.isArray(value2)) return void 0;
|
|
16116
|
+
const entries = [];
|
|
16117
|
+
for (const raw of value2) {
|
|
16118
|
+
if (!isRecord$1(raw)) continue;
|
|
16119
|
+
const name = nonEmptyString(raw.name);
|
|
16120
|
+
const status = typeof raw.status === "string" && MCP_RUNTIME_STATUSES.has(raw.status) ? raw.status : void 0;
|
|
16121
|
+
const checkedAt = optionalTimestamp(raw.checkedAt);
|
|
16122
|
+
if (!name || !status || checkedAt === void 0) continue;
|
|
16123
|
+
const error = nonEmptyString(raw.error);
|
|
16124
|
+
entries.push({ name, status, checkedAt, ...error ? { error } : {} });
|
|
16125
|
+
}
|
|
16126
|
+
return entries.length > 0 ? entries : void 0;
|
|
16127
|
+
}
|
|
16004
16128
|
function coerceCreatedBy(value2) {
|
|
16005
16129
|
if (!isRecord$1(value2)) return void 0;
|
|
16006
16130
|
const accountId = nonEmptyString(value2.accountId);
|
|
@@ -16035,6 +16159,7 @@ function apiSessionToSession(api, candidateKeys, existing) {
|
|
|
16035
16159
|
const branchSlug = branchSlugFromMetadata(metadata) ?? existing?.branchSlug;
|
|
16036
16160
|
const path = nonEmptyString(metadata?.path) ?? existing?.path;
|
|
16037
16161
|
const promptSuggestion2 = metadata && Object.prototype.hasOwnProperty.call(metadata, "promptSuggestion") ? coercePromptSuggestion(metadata.promptSuggestion) : existing?.promptSuggestion;
|
|
16162
|
+
const mcpServers = coerceMcpServerStatuses(metadata?.mcpServers) ?? existing?.mcpServers;
|
|
16038
16163
|
return {
|
|
16039
16164
|
...existing,
|
|
16040
16165
|
id: api.id,
|
|
@@ -16061,6 +16186,7 @@ function apiSessionToSession(api, candidateKeys, existing) {
|
|
|
16061
16186
|
...currentModelCode ? { currentModelCode } : {},
|
|
16062
16187
|
...tokenUsage ? { tokenUsage } : {},
|
|
16063
16188
|
...slashCommands ? { slashCommands } : {},
|
|
16189
|
+
...mcpServers ? { mcpServers } : {},
|
|
16064
16190
|
...claudeSessionId ? { claudeSessionId } : {},
|
|
16065
16191
|
...codexThreadId ? { codexThreadId } : {},
|
|
16066
16192
|
...lifecycleState ? { lifecycleState } : {},
|
|
@@ -16103,9 +16229,10 @@ function readMcpCallerGrantPublicKey(daemonState) {
|
|
|
16103
16229
|
const value2 = daemonState.mcpCallerGrantPublicKey;
|
|
16104
16230
|
return typeof value2 === "string" && value2 ? value2 : void 0;
|
|
16105
16231
|
}
|
|
16232
|
+
const CLEARABLE_STRING_KEYS = /* @__PURE__ */ new Set(["branch", "baseBranch"]);
|
|
16106
16233
|
function buildWorktreeMutationBody(input, options = {}) {
|
|
16107
16234
|
return JSON.stringify(Object.fromEntries(
|
|
16108
|
-
Object.entries(input).filter(([key, value2]) => (key !== "machinePlatform" || options.includeMachinePlatform) && (typeof value2 === "string" && (value2.length > 0 || key
|
|
16235
|
+
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)))
|
|
16109
16236
|
));
|
|
16110
16237
|
}
|
|
16111
16238
|
const STANDALONE_USER_ID = "standalone-local";
|
|
@@ -16642,6 +16769,117 @@ class ApiClient {
|
|
|
16642
16769
|
});
|
|
16643
16770
|
return data.items ?? [];
|
|
16644
16771
|
}
|
|
16772
|
+
/**
|
|
16773
|
+
* 관리자가 보낸 플랫폼 공지(`kind: 'platform_notice'`)만 골라 돌려준다. 다른 kind
|
|
16774
|
+
* (`chat_completion`)는 데스크탑이 이미 스스로 만들고 있어 동기화 대상이 아니다.
|
|
16775
|
+
* specs/desktop-platform-notice-inbox
|
|
16776
|
+
*/
|
|
16777
|
+
async listServerNotifications() {
|
|
16778
|
+
const data = await this.request("/api/me/completion-notifications", { boundary: "api" });
|
|
16779
|
+
return parseServerCompletionNotifications(data);
|
|
16780
|
+
}
|
|
16781
|
+
/**
|
|
16782
|
+
* 서버 알림함에 읽음을 반영한다. `kind` 를 반드시 넘긴다 — 서버 유니크 키가
|
|
16783
|
+
* (userId, kind, sessionId) 라 kind 없이는 같은 세션의 어느 kind를 읽음 처리할지
|
|
16784
|
+
* 서버가 구분하지 못한다. 실패해도 로컬 상태는 이미 반영됐으므로 호출부가 삼킨다.
|
|
16785
|
+
* specs/desktop-platform-notice-inbox R4
|
|
16786
|
+
*/
|
|
16787
|
+
async markServerNotificationRead(sessionId, kind) {
|
|
16788
|
+
await this.request(`/api/me/completion-notifications/${encodeURIComponent(sessionId)}/read?kind=${encodeURIComponent(kind)}`, {
|
|
16789
|
+
method: "PATCH",
|
|
16790
|
+
boundary: "api"
|
|
16791
|
+
});
|
|
16792
|
+
}
|
|
16793
|
+
/**
|
|
16794
|
+
* 관련 프로젝트 등록 목록. 서버가 양방향으로 조회해 준다(대상으로 등록한 쪽도
|
|
16795
|
+
* 나온다). specs/desktop-related-project-handoff
|
|
16796
|
+
*/
|
|
16797
|
+
async listProjectLinks(projectId) {
|
|
16798
|
+
const data = await this.request(
|
|
16799
|
+
`/api/projects/${encodeURIComponent(projectId)}/links`,
|
|
16800
|
+
{ boundary: "api" }
|
|
16801
|
+
);
|
|
16802
|
+
return data.links ?? [];
|
|
16803
|
+
}
|
|
16804
|
+
async addProjectLink(projectId, targetProjectId, role) {
|
|
16805
|
+
const data = await this.request(
|
|
16806
|
+
`/api/projects/${encodeURIComponent(projectId)}/links`,
|
|
16807
|
+
{ method: "POST", boundary: "api", body: JSON.stringify({ targetProjectId, role }) }
|
|
16808
|
+
);
|
|
16809
|
+
return data.link;
|
|
16810
|
+
}
|
|
16811
|
+
async removeProjectLink(projectId, targetProjectId) {
|
|
16812
|
+
await this.request(`/api/projects/${encodeURIComponent(projectId)}/links`, {
|
|
16813
|
+
method: "DELETE",
|
|
16814
|
+
boundary: "api",
|
|
16815
|
+
body: JSON.stringify({ targetProjectId })
|
|
16816
|
+
});
|
|
16817
|
+
}
|
|
16818
|
+
/** 진행 중(queued/running/blocked) 핸드오프 전체 — 전역 인디케이터용. */
|
|
16819
|
+
async listActiveProjectHandoffs() {
|
|
16820
|
+
const data = await this.request("/api/handoffs", { boundary: "api" });
|
|
16821
|
+
return data.handoffs ?? [];
|
|
16822
|
+
}
|
|
16823
|
+
/** 이 프로젝트에서 내보낸 핸드오프 전체 — 프로젝트 설정 화면용. */
|
|
16824
|
+
async listProjectHandoffsForProject(projectId) {
|
|
16825
|
+
const data = await this.request(
|
|
16826
|
+
`/api/projects/${encodeURIComponent(projectId)}/handoffs`,
|
|
16827
|
+
{ boundary: "api" }
|
|
16828
|
+
);
|
|
16829
|
+
return data.handoffs ?? [];
|
|
16830
|
+
}
|
|
16831
|
+
/**
|
|
16832
|
+
* 승인. `sourceSessionId` 는 승인 카드가 렌더된 대화 — 서버가 딥링크 조립에 쓴다.
|
|
16833
|
+
* 이미 승인된(queued 이상) 핸드오프를 다시 승인하면 서버가 409 를 던진다(멱등은
|
|
16834
|
+
* 호출부가 그 에러를 흡수하는 방식으로 구현한다). specs/desktop-related-project-handoff R7
|
|
16835
|
+
*/
|
|
16836
|
+
async approveProjectHandoff(handoffId, sourceSessionId) {
|
|
16837
|
+
const data = await this.request(
|
|
16838
|
+
`/api/handoffs/${encodeURIComponent(handoffId)}/approve`,
|
|
16839
|
+
{ method: "POST", boundary: "api", body: JSON.stringify({ sourceSessionId }) }
|
|
16840
|
+
);
|
|
16841
|
+
return data.handoff;
|
|
16842
|
+
}
|
|
16843
|
+
async rejectProjectHandoff(handoffId) {
|
|
16844
|
+
const data = await this.request(
|
|
16845
|
+
`/api/handoffs/${encodeURIComponent(handoffId)}/reject`,
|
|
16846
|
+
{ method: "POST", boundary: "api" }
|
|
16847
|
+
);
|
|
16848
|
+
return data.handoff;
|
|
16849
|
+
}
|
|
16850
|
+
/** 대상 프로젝트에 실제 세션을 만든 뒤 보고한다. 서버가 targetSessionId 기준으로
|
|
16851
|
+
* 멱등을 보장한다 — 이미 있으면 새 매핑을 만들지 않고 그대로 돌려준다. */
|
|
16852
|
+
async reportProjectHandoffStarted(handoffId, targetSessionId) {
|
|
16853
|
+
const data = await this.request(
|
|
16854
|
+
`/api/handoffs/${encodeURIComponent(handoffId)}/started`,
|
|
16855
|
+
{ method: "POST", boundary: "api", body: JSON.stringify({ targetSessionId }) }
|
|
16856
|
+
);
|
|
16857
|
+
return data.handoff;
|
|
16858
|
+
}
|
|
16859
|
+
/** 대상 프로젝트에 온라인 머신이 없는 등, 세션 생성 자체를 시작하지 못했을 때. */
|
|
16860
|
+
async reportProjectHandoffBlocked(handoffId, reason) {
|
|
16861
|
+
const data = await this.request(
|
|
16862
|
+
`/api/handoffs/${encodeURIComponent(handoffId)}/blocked`,
|
|
16863
|
+
{ method: "POST", boundary: "api", body: JSON.stringify({ reason }) }
|
|
16864
|
+
);
|
|
16865
|
+
return data.handoff;
|
|
16866
|
+
}
|
|
16867
|
+
/** 세션 생성 시도가 던졌을 때. */
|
|
16868
|
+
async reportProjectHandoffFailed(handoffId, reason) {
|
|
16869
|
+
const data = await this.request(
|
|
16870
|
+
`/api/handoffs/${encodeURIComponent(handoffId)}/failed`,
|
|
16871
|
+
{ method: "POST", boundary: "api", body: JSON.stringify({ reason }) }
|
|
16872
|
+
);
|
|
16873
|
+
return data.handoff;
|
|
16874
|
+
}
|
|
16875
|
+
/** 기본 꺼짐 — request_project_handoff MCP 도구 노출 토글(spec R9). */
|
|
16876
|
+
async setProjectHandoffMcpEnabled(projectId, enabled) {
|
|
16877
|
+
return this.request(`/api/projects/${encodeURIComponent(projectId)}/handoff-mcp-enabled`, {
|
|
16878
|
+
method: "PATCH",
|
|
16879
|
+
boundary: "api",
|
|
16880
|
+
body: JSON.stringify({ enabled })
|
|
16881
|
+
});
|
|
16882
|
+
}
|
|
16645
16883
|
async addProjectFavorite(projectId) {
|
|
16646
16884
|
return this.request(`/api/projects/${encodeURIComponent(projectId)}/favorite`, {
|
|
16647
16885
|
method: "PUT",
|
|
@@ -16657,6 +16895,7 @@ class ApiClient {
|
|
|
16657
16895
|
// --- 개인 Chat (specs/personal-chats) ---
|
|
16658
16896
|
async listPersonalChats(input = {}) {
|
|
16659
16897
|
const params = new URLSearchParams();
|
|
16898
|
+
params.set("context", input.companyId ? `company:${input.companyId}` : "personal");
|
|
16660
16899
|
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
16661
16900
|
if (input.offset !== void 0) params.set("offset", String(input.offset));
|
|
16662
16901
|
if (input.query) params.set("query", input.query);
|
|
@@ -16672,12 +16911,22 @@ class ApiClient {
|
|
|
16672
16911
|
const data = await this.request("/api/personal-chats", {
|
|
16673
16912
|
method: "POST",
|
|
16674
16913
|
boundary: "api",
|
|
16675
|
-
body: JSON.stringify(
|
|
16676
|
-
|
|
16677
|
-
|
|
16914
|
+
body: JSON.stringify({
|
|
16915
|
+
machineId: input.machineId,
|
|
16916
|
+
...input.title === void 0 ? {} : { title: input.title },
|
|
16917
|
+
..."companyId" in input ? { companyId: input.companyId } : {}
|
|
16918
|
+
})
|
|
16678
16919
|
});
|
|
16679
16920
|
return data.chat;
|
|
16680
16921
|
}
|
|
16922
|
+
async getPersonalChatRuntimeConfig(chatId, machineId) {
|
|
16923
|
+
const params = new URLSearchParams({ machineId });
|
|
16924
|
+
const data = await this.request(
|
|
16925
|
+
`/api/personal-chats/${encodeURIComponent(chatId)}/runtime-config?${params.toString()}`,
|
|
16926
|
+
{ boundary: "api", headers: { "Cache-Control": "no-cache" } }
|
|
16927
|
+
);
|
|
16928
|
+
return data.environmentVariables ?? {};
|
|
16929
|
+
}
|
|
16681
16930
|
async getPersonalChat(chatId) {
|
|
16682
16931
|
const data = await this.request(
|
|
16683
16932
|
`/api/personal-chats/${encodeURIComponent(chatId)}`,
|
|
@@ -17003,19 +17252,23 @@ class ApiClient {
|
|
|
17003
17252
|
}
|
|
17004
17253
|
/**
|
|
17005
17254
|
* Mint the signed caller grant that proves *this* user is the connector
|
|
17006
|
-
* subject of a session spawned on a
|
|
17007
|
-
* userId/machineId/projectId/expiry and rejects a caller
|
|
17008
|
-
*
|
|
17009
|
-
* is required by the route —
|
|
17010
|
-
*
|
|
17255
|
+
* subject of a session spawned on a grant-capable machine. The server binds it to
|
|
17256
|
+
* userId/machineId/projectId-or-personalChatId/expiry and rejects a caller
|
|
17257
|
+
* who cannot access that runtime scope (web-ui server/mcpCallerGrantRoute.ts).
|
|
17258
|
+
* `projectId` is required by the route — Personal Chat grants send an explicit
|
|
17259
|
+
* null plus `personalChatId` so the signed scope matches the spawn.
|
|
17011
17260
|
*/
|
|
17012
|
-
async mintMcpCallerGrant(machineId, projectId) {
|
|
17261
|
+
async mintMcpCallerGrant(machineId, projectId, personalChatId) {
|
|
17013
17262
|
const data = await this.request(
|
|
17014
17263
|
"/api/me/mcp-caller-grant",
|
|
17015
17264
|
{
|
|
17016
17265
|
method: "POST",
|
|
17017
17266
|
boundary: "api",
|
|
17018
|
-
body: JSON.stringify({
|
|
17267
|
+
body: JSON.stringify({
|
|
17268
|
+
machineId,
|
|
17269
|
+
projectId,
|
|
17270
|
+
...personalChatId ? { personalChatId } : {}
|
|
17271
|
+
})
|
|
17019
17272
|
}
|
|
17020
17273
|
);
|
|
17021
17274
|
if (typeof data.grant !== "string" || !data.grant) throw new Error("mcp caller grant is missing");
|
|
@@ -23394,7 +23647,7 @@ ${text2}` : text2);
|
|
|
23394
23647
|
return sections.join("\n\n");
|
|
23395
23648
|
}
|
|
23396
23649
|
const HANDOFF_READ_MESSAGES = 400;
|
|
23397
|
-
async function handoffToSibling(runtime, scope, env, handoff, agent) {
|
|
23650
|
+
async function handoffToSibling(runtime, scope, env, handoff, agent, modelOptions = {}) {
|
|
23398
23651
|
const from = handoff.from ?? env.sessionId;
|
|
23399
23652
|
if (!from) {
|
|
23400
23653
|
return agentFail(
|
|
@@ -23433,6 +23686,7 @@ async function handoffToSibling(runtime, scope, env, handoff, agent) {
|
|
|
23433
23686
|
sessionId = (await runtime.spawnSession({
|
|
23434
23687
|
directory: env.root,
|
|
23435
23688
|
agent,
|
|
23689
|
+
...modelOptions,
|
|
23436
23690
|
initialPrompt: prompt,
|
|
23437
23691
|
environmentVariables: childAgentEnv(env, childId)
|
|
23438
23692
|
})).sessionId;
|
|
@@ -23538,7 +23792,7 @@ function resolveSubagentControlPermission(records, parentSessionId, childSession
|
|
|
23538
23792
|
return valid ? isDescendant(valid, parentSessionId, childSessionId, 2) : false;
|
|
23539
23793
|
}
|
|
23540
23794
|
function canControlSpawnedDescendant(records, parentSessionId, childSessionId) {
|
|
23541
|
-
const lineage = records.flatMap((record2) => record2.outcome === "spawned" && record2.parentSessionId && record2.childSessionId ? [{
|
|
23795
|
+
const lineage = records.flatMap((record2) => record2.outcome === "spawned" && record2.parentSessionId && record2.childSessionId && record2.parentSessionId !== record2.childSessionId && Number.isFinite(record2.ts) && record2.ts >= 0 ? [{
|
|
23542
23796
|
parentSessionId: record2.parentSessionId,
|
|
23543
23797
|
childSessionId: record2.childSessionId,
|
|
23544
23798
|
spawnedAt: record2.ts,
|
|
@@ -23579,24 +23833,27 @@ async function stopOwnedTarget(runtime, env, target) {
|
|
|
23579
23833
|
const refused = ownershipFailure(runtime, env, target);
|
|
23580
23834
|
if (refused) return refused;
|
|
23581
23835
|
if (!runtime.stopSession) return agentFail("stop_rejected", "stop transport is unavailable");
|
|
23582
|
-
let stream2 = null;
|
|
23583
|
-
try {
|
|
23584
|
-
stream2 = await runtime.openStream(target.session.id);
|
|
23585
|
-
if (stream2.current().thinking !== true) {
|
|
23586
|
-
return agentFail("stop_not_responding", `${target.session.id} has no active response`);
|
|
23587
|
-
}
|
|
23588
|
-
} catch (error) {
|
|
23589
|
-
return agentFail("stop_rejected", error instanceof Error ? error.message : String(error));
|
|
23590
|
-
} finally {
|
|
23591
|
-
stream2?.close();
|
|
23592
|
-
}
|
|
23593
23836
|
try {
|
|
23594
23837
|
const result = await runtime.stopSession(target.session.machineId, target.session.id);
|
|
23595
|
-
|
|
23838
|
+
if (result.stopped) return agentOk({ sessionId: target.session.id, stopped: true });
|
|
23839
|
+
return result.reason === "not-responding" ? agentFail("stop_not_responding", result.error) : agentFail("stop_rejected", result.error);
|
|
23596
23840
|
} catch (error) {
|
|
23597
23841
|
return agentFail("stop_rejected", error instanceof Error ? error.message : String(error));
|
|
23598
23842
|
}
|
|
23599
23843
|
}
|
|
23844
|
+
const CLI_CONFIGURABLE_AGENTS = [.../* @__PURE__ */ new Set([
|
|
23845
|
+
...newSessionAgents.map(({ id }) => id),
|
|
23846
|
+
"gemini"
|
|
23847
|
+
])];
|
|
23848
|
+
function cliSpawnModelOptions() {
|
|
23849
|
+
return CLI_CONFIGURABLE_AGENTS.map((agent) => ({
|
|
23850
|
+
agent,
|
|
23851
|
+
models: modelChoices(agent).map(({ key: id }) => ({
|
|
23852
|
+
id,
|
|
23853
|
+
efforts: effortChoices(agent, id).map(({ key }) => key)
|
|
23854
|
+
}))
|
|
23855
|
+
}));
|
|
23856
|
+
}
|
|
23600
23857
|
const DEFAULT_READ_LIMIT = 20;
|
|
23601
23858
|
const STATE_WINDOW_MESSAGES = 50;
|
|
23602
23859
|
function scopeOf(runtime) {
|
|
@@ -23650,7 +23907,7 @@ async function readSession(runtime, target, last) {
|
|
|
23650
23907
|
});
|
|
23651
23908
|
}
|
|
23652
23909
|
const DEFAULT_SPAWN_AGENT = "claude";
|
|
23653
|
-
async function spawnSibling(runtime, env, text2, agent, contextFiles = []) {
|
|
23910
|
+
async function spawnSibling(runtime, env, text2, agent, contextFiles = [], modelOptions = {}) {
|
|
23654
23911
|
if (env.depth >= AGENT_MAX_DEPTH) {
|
|
23655
23912
|
return agentFail("spawn_depth_exceeded", `depth ${env.depth} is already at the ceiling of ${AGENT_MAX_DEPTH}`);
|
|
23656
23913
|
}
|
|
@@ -23678,6 +23935,7 @@ async function spawnSibling(runtime, env, text2, agent, contextFiles = []) {
|
|
|
23678
23935
|
sessionId = (await runtime.spawnSession({
|
|
23679
23936
|
directory: env.root,
|
|
23680
23937
|
agent,
|
|
23938
|
+
...modelOptions,
|
|
23681
23939
|
initialPrompt: composeSpawnPrompt(text2, context.paths),
|
|
23682
23940
|
environmentVariables: childAgentEnv(env, childId)
|
|
23683
23941
|
})).sessionId;
|
|
@@ -23727,7 +23985,8 @@ async function runAgentCommand(runtime, args) {
|
|
|
23727
23985
|
maxSpawn: env.maxSpawn,
|
|
23728
23986
|
spawned,
|
|
23729
23987
|
spawnsRemaining: spawned === null ? null : Math.max(0, env.maxSpawn - spawned),
|
|
23730
|
-
canSpawn: env.depth < AGENT_MAX_DEPTH &&
|
|
23988
|
+
canSpawn: env.depth < AGENT_MAX_DEPTH && spawned !== null && spawned < env.maxSpawn,
|
|
23989
|
+
spawnModelOptions: cliSpawnModelOptions()
|
|
23731
23990
|
});
|
|
23732
23991
|
}
|
|
23733
23992
|
if (args.verb === "ls") {
|
|
@@ -23735,10 +23994,13 @@ async function runAgentCommand(runtime, args) {
|
|
|
23735
23994
|
return agentOk({ sessions: sessions.filter((session) => isInScope(session, scope)).map(sessionSummary) });
|
|
23736
23995
|
}
|
|
23737
23996
|
if (args.verb === "spawn") {
|
|
23997
|
+
const agent = args.agent ?? DEFAULT_SPAWN_AGENT;
|
|
23998
|
+
const modelOptions = args.model !== void 0 || args.effort !== void 0 ? await validateSpawnModelOptions(agent, args.model, args.effort) : {};
|
|
23999
|
+
if (isFailure(modelOptions)) return modelOptions;
|
|
23738
24000
|
if (args.handoff) {
|
|
23739
|
-
return handoffToSibling(runtime, scope, env, args.handoff,
|
|
24001
|
+
return handoffToSibling(runtime, scope, env, args.handoff, agent, modelOptions);
|
|
23740
24002
|
}
|
|
23741
|
-
return spawnSibling(runtime, env, args.text ?? "",
|
|
24003
|
+
return spawnSibling(runtime, env, args.text ?? "", agent, args.files ?? [], modelOptions);
|
|
23742
24004
|
}
|
|
23743
24005
|
if (!args.sessionId) return agentFail("session_not_found", "no session id given");
|
|
23744
24006
|
const target = await resolveTarget(runtime, scope, args.sessionId);
|
|
@@ -23763,6 +24025,17 @@ async function runAgentCommand(runtime, args) {
|
|
|
23763
24025
|
return agentFail("session_not_found", `unsupported verb "${args.verb}"`);
|
|
23764
24026
|
}
|
|
23765
24027
|
}
|
|
24028
|
+
async function validateSpawnModelOptions(agent, model, effort) {
|
|
24029
|
+
try {
|
|
24030
|
+
await resolveCliNewConversationOptions({ agent, model, effort });
|
|
24031
|
+
return {
|
|
24032
|
+
...model !== void 0 && model !== "default" ? { model } : {},
|
|
24033
|
+
...effort !== void 0 ? { effort } : {}
|
|
24034
|
+
};
|
|
24035
|
+
} catch (error) {
|
|
24036
|
+
return agentFail("invalid_spawn_options", error instanceof Error ? error.message : String(error));
|
|
24037
|
+
}
|
|
24038
|
+
}
|
|
23766
24039
|
function parseSpawnRecord(value2) {
|
|
23767
24040
|
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
|
|
23768
24041
|
const record2 = value2;
|
|
@@ -23857,25 +24130,33 @@ async function fileSessionUnderProject(context, env, input) {
|
|
|
23857
24130
|
if (!viaWorktree) return;
|
|
23858
24131
|
await context.rest.assignSessionToProject(viaWorktree.projectId, input.sessionId, viaWorktree.worktreeId);
|
|
23859
24132
|
}
|
|
23860
|
-
|
|
23861
|
-
|
|
23862
|
-
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
23873
|
-
|
|
23874
|
-
|
|
23875
|
-
|
|
24133
|
+
const ACTIVITY_WAIT_TIMEOUT_MS = 5e3;
|
|
24134
|
+
function waitForResponseActivity(transport, sessionId) {
|
|
24135
|
+
return new Promise((resolve) => {
|
|
24136
|
+
let unsubscribe = () => {
|
|
24137
|
+
};
|
|
24138
|
+
const finish = (thinking) => {
|
|
24139
|
+
clearTimeout(timer);
|
|
24140
|
+
unsubscribe();
|
|
24141
|
+
resolve(thinking);
|
|
24142
|
+
};
|
|
24143
|
+
const timer = setTimeout(() => finish(null), ACTIVITY_WAIT_TIMEOUT_MS);
|
|
24144
|
+
unsubscribe = transport.onEphemeral((event) => {
|
|
24145
|
+
if (!event || typeof event !== "object") return;
|
|
24146
|
+
const activity = event;
|
|
24147
|
+
if (activity.type !== "activity" || activity.id !== sessionId || typeof activity.thinking !== "boolean") return;
|
|
24148
|
+
finish(activity.thinking);
|
|
24149
|
+
});
|
|
24150
|
+
});
|
|
23876
24151
|
}
|
|
23877
24152
|
async function stopAgentSession(transport, machineId, sessionId) {
|
|
23878
24153
|
try {
|
|
24154
|
+
const thinking = await waitForResponseActivity(transport, sessionId);
|
|
24155
|
+
if (thinking !== true) return {
|
|
24156
|
+
stopped: false,
|
|
24157
|
+
reason: thinking === false ? "not-responding" : "rejected",
|
|
24158
|
+
error: thinking === false ? "Session has no active response." : "Could not confirm the session response state."
|
|
24159
|
+
};
|
|
23879
24160
|
const result = await transport.rpc(machineId, "stop-session", {
|
|
23880
24161
|
sessionId,
|
|
23881
24162
|
source: "saycode-agent-stop",
|
|
@@ -23883,17 +24164,37 @@ async function stopAgentSession(transport, machineId, sessionId) {
|
|
|
23883
24164
|
mode: "force"
|
|
23884
24165
|
});
|
|
23885
24166
|
if (result?.stopped === true) return { stopped: true };
|
|
23886
|
-
return {
|
|
24167
|
+
if (result?.stopped === false) return {
|
|
23887
24168
|
stopped: false,
|
|
23888
|
-
|
|
24169
|
+
reason: "not-responding",
|
|
24170
|
+
error: typeof result.reason === "string" ? result.reason : "Session has no active response."
|
|
23889
24171
|
};
|
|
24172
|
+
return { stopped: false, reason: "rejected", error: "Machine returned an invalid stop acknowledgement." };
|
|
23890
24173
|
} catch (error) {
|
|
23891
24174
|
return {
|
|
23892
24175
|
stopped: false,
|
|
24176
|
+
reason: "rejected",
|
|
23893
24177
|
error: error instanceof Error ? error.message : String(error)
|
|
23894
24178
|
};
|
|
23895
24179
|
}
|
|
23896
24180
|
}
|
|
24181
|
+
function rejectedSteer(error) {
|
|
24182
|
+
return {
|
|
24183
|
+
accepted: false,
|
|
24184
|
+
reason: /\bin[- ]flight\b/i.test(error) ? "in-flight" : "rejected",
|
|
24185
|
+
error
|
|
24186
|
+
};
|
|
24187
|
+
}
|
|
24188
|
+
async function steerAgentSession(transport, sessionId, text2) {
|
|
24189
|
+
try {
|
|
24190
|
+
const result = await transport.rpc(sessionId, "steer", { text: text2 });
|
|
24191
|
+
if (result?.success === true) return { accepted: true };
|
|
24192
|
+
const error = typeof result?.error === "string" ? result.error : "Session returned an invalid steer acknowledgement.";
|
|
24193
|
+
return rejectedSteer(error);
|
|
24194
|
+
} catch (error) {
|
|
24195
|
+
return rejectedSteer(error instanceof Error ? error.message : String(error));
|
|
24196
|
+
}
|
|
24197
|
+
}
|
|
23897
24198
|
const STREAM_HISTORY_LIMIT = 50;
|
|
23898
24199
|
const SPAWN_RPC_TIMEOUT_MS = 6e4;
|
|
23899
24200
|
function isReadableFileSync(path) {
|
|
@@ -24005,6 +24306,8 @@ function createAgentRuntime(context, transport, env = process.env) {
|
|
|
24005
24306
|
{
|
|
24006
24307
|
directory: input.directory,
|
|
24007
24308
|
agent: input.agent,
|
|
24309
|
+
...input.model ? { model: input.model } : {},
|
|
24310
|
+
...input.effort ? { effort: input.effort } : {},
|
|
24008
24311
|
initialPrompt: input.initialPrompt,
|
|
24009
24312
|
environmentVariables: input.environmentVariables
|
|
24010
24313
|
},
|