aiterm-mcp 0.58.4 → 0.59.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/CHANGELOG.md +79 -1
- package/README.ja.md +15 -5
- package/README.md +14 -4
- package/dist/agent-shared.js +12 -1
- package/dist/core.js +868 -106
- package/dist/cursor-parent-receive.js +2 -1
- package/dist/fast-mode.js +22 -0
- package/dist/harnesses/claude.js +137 -19
- package/dist/harnesses/codex.js +95 -3
- package/dist/harnesses/cursor.js +96 -12
- package/dist/harnesses/grok.js +159 -2
- package/dist/index.js +41 -7
- package/dist/live-node.js +17 -0
- package/dist/model-catalog.js +1 -0
- package/dist/parent-delivery-cli.js +3 -2
- package/dist/runtime-error-report.js +2 -1
- package/dist/runtime-error-store.js +3 -2
- package/dist/user-config.js +138 -0
- package/docs/DESIGN.md +11 -0
- package/docs/RELEASE.md +5 -0
- package/package.json +2 -2
package/dist/core.js
CHANGED
|
@@ -17,14 +17,18 @@ import { isCursorMcpClient } from "aiterm-steer-delivery";
|
|
|
17
17
|
import { paneTokenHint } from "./harnesses/pane-tokens.js";
|
|
18
18
|
import { unfinishedDeliveriesOwnedBy } from "./parent-delivery-owners.js";
|
|
19
19
|
import { readRuntimeProcesses, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost, processElapsedSeconds } from "./process-runtime.js";
|
|
20
|
+
import { liveNodeExecutable } from "./live-node.js";
|
|
20
21
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
21
22
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, openPsmuxServerToDesktop, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
|
|
22
|
-
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
23
|
+
import { AGENT_DEFAULT_PREFACE, sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_RATE_LIMIT_POLL_MS, pollGate, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
23
24
|
import { catalogUnavailable } from "./model-catalog.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
25
|
+
import { FastModeError, fastModeFailure } from "./fast-mode.js";
|
|
26
|
+
import { readFileOrNull, restoreJsonSetting, withUserConfigLock } from "./user-config.js";
|
|
27
|
+
import { isDeepStrictEqual } from "node:util";
|
|
28
|
+
import { grokAuthPlan, grokAuthStatus, grokAuthPane, GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokModelChoices, grokModelFromFooter, grokFooterModelName, grokModelDefaultsFromBody, restoreGrokModelDefaults, grokEventsTranscript, latestGrokCompletion, grokTurnSettled, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, grokTuiBusy, grokPaneObservation, grokRateLimitDialog, grokPromptAwaitingAccept, grokUnacceptedPromptRestored, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
29
|
+
import { codexAuthPlan, codexAuthStatus, codexAuthPane, realCodexHome, readCodexConfigPins, bindCodexTranscriptSession, latestCodexCompletion, codexTurnSettled, codexRunningTurn, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, codexTuiBusy, codexHelperProcess, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, codexModelChoices, codexFastCommandOffered, codexFooterShowsFast, codexServiceTierNotices, restoreCodexDefaults, restoreCodexDefaultInFile, } from "./harnesses/codex.js";
|
|
30
|
+
import { claudeAuthPlan, claudeAuthStatus, claudeAuthPane, OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, claudeModelChoices, claudeConfigDir, claudeFastIndicatorShown, claudeFastResult, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, claudePaneObservation, claudeUsageLimit, claudeLiveSelectMenu, claudeModelPicker, claudeModelPickerRow, claudeModelSwitchConfirm, claudeModelSwitchResult, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
|
|
31
|
+
import { cursorAuthPlan, cursorAuthStatus, cursorAuthPane, bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, cursorModelChoices, cursorModelIsFastVariant, cursorFastParameter, cursorHeaderShowsFast, cursorPickerView, cursorEffortSelected, cursorParameterRows, cursorUserConfigPath, CURSOR_DEFAULT_MODEL_KEYS, realCursorHome, buildCursorAgentCmd, cursorAgentArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
28
32
|
import { readInterimWords, recordInterimBoundary } from "./interim-words.js";
|
|
29
33
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
30
34
|
export { AitermError } from "./errors.js";
|
|
@@ -786,13 +790,27 @@ export function assertAgentPreface(preface) {
|
|
|
786
790
|
}
|
|
787
791
|
return preface;
|
|
788
792
|
}
|
|
793
|
+
// Claude Codeが、貼り付けの印で入った文を`<pasted_content>`で包む境目。2.1.296の実物の席で測った:
|
|
794
|
+
// 改行が3つ以上(4行以上)か、800字を超える文は包む。3行まで・800字以内は包まない。
|
|
795
|
+
const CLAUDE_PASTE_WRAP_NEWLINES = 3;
|
|
796
|
+
const CLAUDE_PASTE_WRAP_CHARS = 800;
|
|
797
|
+
/** Claude Codeが、この文を貼り付けとして包むか。境目ちょうど(800字)は包む側に倒す(前置きが余分に付くだけで済む)。 */
|
|
798
|
+
export function claudeWrapsPaste(text) {
|
|
799
|
+
return text.length >= CLAUDE_PASTE_WRAP_CHARS || (text.match(/\n/gu)?.length ?? 0) >= CLAUDE_PASTE_WRAP_NEWLINES;
|
|
800
|
+
}
|
|
801
|
+
export { AGENT_DEFAULT_PREFACE };
|
|
789
802
|
/**
|
|
790
803
|
* 前置きつきの送信文を、harnessごとの形へ分ける。Claude Codeは前置きを印なしで入れ、空行と本文を貼る。
|
|
791
804
|
* Codex・Grok・Cursorは貼り付けを包まないので、前置き・空行・本文を1つにつないで貼る(渡る文は前置きなしの時と同じ並び)。
|
|
805
|
+
* `preface`が無い時、Claude Codeが包む長さの文には、Aitermの既定の1行を前置きにする(`defaultForWrapped`がtrueの時だけ)。
|
|
792
806
|
*/
|
|
793
|
-
function splitAgentPreface(kind, preface, text) {
|
|
794
|
-
if (preface == null)
|
|
807
|
+
function splitAgentPreface(kind, preface, text, o = {}) {
|
|
808
|
+
if (preface == null) {
|
|
809
|
+
if (o.defaultForWrapped && kind === "claude" && claudeWrapsPaste(text)) {
|
|
810
|
+
return { typedPreface: AGENT_DEFAULT_PREFACE, body: `\n\n${text}` };
|
|
811
|
+
}
|
|
795
812
|
return { body: text };
|
|
813
|
+
}
|
|
796
814
|
assertAgentPreface(preface);
|
|
797
815
|
return kind === "claude" ? { typedPreface: preface, body: `\n\n${text}` } : { body: `${preface}\n\n${text}` };
|
|
798
816
|
}
|
|
@@ -2413,6 +2431,11 @@ function loadAgentMetadata(name) {
|
|
|
2413
2431
|
initial_prompt_delivery: m.initial_prompt_delivery, initial_prompt_cursor: m.initial_prompt_cursor,
|
|
2414
2432
|
};
|
|
2415
2433
|
const executableFields = m.agent_executable === undefined ? {} : { agent_executable: m.agent_executable };
|
|
2434
|
+
// 席のファストの状態と、起動・設定替えで渡したmodel(ADR 0114)。この項目より前の版が書いた控えには無い。
|
|
2435
|
+
const fastFields = {
|
|
2436
|
+
...(typeof m.fast === "boolean" ? { fast: m.fast } : {}),
|
|
2437
|
+
...(typeof m.model === "string" || m.model === null ? { model: m.model } : {}),
|
|
2438
|
+
};
|
|
2416
2439
|
const startupShapes = Array.isArray(m.startup_process_shapes) && m.startup_process_shapes.every((shape) => shape && typeof shape.identity === "string" && (shape.parent === null || typeof shape.parent === "string") && typeof shape.argv_digest === "string")
|
|
2417
2440
|
? { startup_process_shapes: m.startup_process_shapes } : {};
|
|
2418
2441
|
const startupFields = Array.isArray(m.startup_processes) && m.startup_processes.every((identity) => typeof identity === "string")
|
|
@@ -2444,6 +2467,7 @@ function loadAgentMetadata(name) {
|
|
|
2444
2467
|
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
2445
2468
|
...deliveryFields,
|
|
2446
2469
|
...executableFields,
|
|
2470
|
+
...fastFields,
|
|
2447
2471
|
...startupFields,
|
|
2448
2472
|
launch_operation_id: launchOperationId,
|
|
2449
2473
|
launch_request_digest: launchRequestDigest,
|
|
@@ -2476,6 +2500,7 @@ function loadAgentMetadata(name) {
|
|
|
2476
2500
|
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
2477
2501
|
...deliveryFields,
|
|
2478
2502
|
...executableFields,
|
|
2503
|
+
...fastFields,
|
|
2479
2504
|
...startupFields,
|
|
2480
2505
|
hook_route: "shared_codex_home",
|
|
2481
2506
|
completion_route: "codex_transcript",
|
|
@@ -2504,6 +2529,7 @@ function loadAgentMetadata(name) {
|
|
|
2504
2529
|
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
2505
2530
|
...deliveryFields,
|
|
2506
2531
|
...executableFields,
|
|
2532
|
+
...fastFields,
|
|
2507
2533
|
...startupFields,
|
|
2508
2534
|
hook_route: "shared_cursor_home",
|
|
2509
2535
|
completion_route: "cursor_transcript",
|
|
@@ -2540,6 +2566,7 @@ function loadAgentMetadata(name) {
|
|
|
2540
2566
|
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
2541
2567
|
...deliveryFields,
|
|
2542
2568
|
...executableFields,
|
|
2569
|
+
...fastFields,
|
|
2543
2570
|
...startupFields,
|
|
2544
2571
|
hook_route: "shared_grok_home",
|
|
2545
2572
|
completion_route: "grok_transcript",
|
|
@@ -3027,7 +3054,7 @@ export function windowsStartProcessArgumentList(args) {
|
|
|
3027
3054
|
// npmのplatform別bin shimをcallerに解釈させず、現在稼働中のNodeと同梱CLIを直接起動する。
|
|
3028
3055
|
// Windowsでもbackendはpsmux、対話shellはPowerShell 7のまま。これはwaiter processの入口だけを所有する。
|
|
3029
3056
|
export function agentWaitProcess(session, cursor, runtime = {}) {
|
|
3030
|
-
const executable = runtime.executable ??
|
|
3057
|
+
const executable = runtime.executable ?? liveNodeExecutable();
|
|
3031
3058
|
const args = [
|
|
3032
3059
|
runtime.cliPath ?? fileURLToPath(new URL("./aiterm-wait-cli.js", import.meta.url)),
|
|
3033
3060
|
"--session",
|
|
@@ -3618,7 +3645,9 @@ export function __testSetAgentTuiReadyStableSamples(value) {
|
|
|
3618
3645
|
agentTuiReadyStableSamplesTestOverride = value;
|
|
3619
3646
|
}
|
|
3620
3647
|
async function sendAgentPromptText(name, text, kind) {
|
|
3621
|
-
|
|
3648
|
+
// 起動時の文にも、包みになる長さなら出所の1行を包みの外へ出す(ADR 0116)。
|
|
3649
|
+
const prefaced = kind === undefined ? { body: text } : splitAgentPreface(kind, null, text, { defaultForWrapped: true });
|
|
3650
|
+
send(name, prefaced.body, {
|
|
3622
3651
|
enter: false,
|
|
3623
3652
|
force: true,
|
|
3624
3653
|
raw: false,
|
|
@@ -3626,6 +3655,7 @@ async function sendAgentPromptText(name, text, kind) {
|
|
|
3626
3655
|
rtk: false,
|
|
3627
3656
|
preserveAgentOperation: true,
|
|
3628
3657
|
bracketedPaste: true,
|
|
3658
|
+
typedPreface: prefaced.typedPreface,
|
|
3629
3659
|
});
|
|
3630
3660
|
if (kind === "cursor") {
|
|
3631
3661
|
const visible = await waitCursorPromptVisible(name, text);
|
|
@@ -3738,6 +3768,7 @@ async function sendInitialAgentPromptInLock(name, text, o) {
|
|
|
3738
3768
|
`agent session '${name}' の起動準備が完了していないため、promptは送信していません。reason=${startup.reason}` +
|
|
3739
3769
|
startupBlockedHint(startup.reason), 2);
|
|
3740
3770
|
}
|
|
3771
|
+
await o.after_ready?.(name, meta);
|
|
3741
3772
|
const promptText = meta.kind === "cursor" ? cursorPromptWithLineage(meta, text) : text;
|
|
3742
3773
|
const startOffset = agentCompletionCursor(meta);
|
|
3743
3774
|
try {
|
|
@@ -4280,16 +4311,550 @@ async function configureClaudeForSessionOnly(name, meta, model, effort) {
|
|
|
4280
4311
|
if (!ready.ready)
|
|
4281
4312
|
throw new AitermError("Claudeのmodel変更完了を確認できません", 2);
|
|
4282
4313
|
}
|
|
4314
|
+
// ===================== ファスト(高速の応答)の切り替え(ADR 0114) =====================
|
|
4315
|
+
// 席は必ず通常の速度で起こす。fast: true は、通常で起こしてから、その席で切り替える(Claude Code・Codex)。
|
|
4316
|
+
// CursorとGrokはfastが別のmodel IDなので、起動時のIDをfast版にする。
|
|
4317
|
+
// harnessの切り替えの命令は利用者の既定も書き換えるので(Cursorを除く)、切り替えの直前の値へ戻す。
|
|
4318
|
+
// 読む→切り替える→戻すは、設定fileごとの鍵(withUserConfigLock)で、同じ端末の全てのAitermの間で1つずつにする。
|
|
4319
|
+
const CLAUDE_FAST_RESULT_TIMEOUT_MS = 20_000;
|
|
4320
|
+
const CODEX_FAST_OFFER_TIMEOUT_MS = 3_000;
|
|
4321
|
+
const CODEX_FAST_NOTICE_TIMEOUT_MS = 8_000;
|
|
4322
|
+
const CURSOR_FAST_ROW_TIMEOUT_MS = 3_000;
|
|
4323
|
+
// Grokは起動して1秒もせずに既定を書く(1.0.50の記録で、起動完了の直後)。書かない時(同じ値、古い版)に起動を長く待たせない。
|
|
4324
|
+
const LAUNCH_DEFAULT_WAIT_MS = 8_000;
|
|
4325
|
+
// 既定が必ず書かれる見込みの時に、書かれるのを待つ上限(Cursorは、遅い起動で入力待ちの数秒後に書く)。
|
|
4326
|
+
const LAUNCH_DEFAULT_MUST_WRITE_WAIT_MS = 25_000;
|
|
4327
|
+
const LAUNCH_ALIVE_CHECK_MS = 1_000;
|
|
4328
|
+
const GROK_FAST_CATALOG_RETRY_MS = 1_500;
|
|
4329
|
+
const GROK_MODEL_SWITCH_TIMEOUT_MS = 10_000;
|
|
4330
|
+
/**
|
|
4331
|
+
* Grokの一覧(fast版の対を読むため)。Grokは、しばらく聞かれていないと最初の答えにfast版が入っていない事がある
|
|
4332
|
+
* (Windowsの1.0.50で見た。直後の聞き直しには入っている)。そのmodelのfast版が見えない時は、少し置いて1度だけ聞き直す。
|
|
4333
|
+
*/
|
|
4334
|
+
async function grokCatalogForFast(bin, cwd, base) {
|
|
4335
|
+
const first = await grokModelChoices(bin, cwd);
|
|
4336
|
+
if (first.fast_ids?.[base])
|
|
4337
|
+
return first;
|
|
4338
|
+
await sleep(GROK_FAST_CATALOG_RETRY_MS);
|
|
4339
|
+
return grokModelChoices(bin, cwd);
|
|
4340
|
+
}
|
|
4341
|
+
/** modelへ直に書かれたIDが、fast版のIDか(fastが別のmodel IDのharnessだけ)。 */
|
|
4342
|
+
function modelIsFastVariant(kind, model) {
|
|
4343
|
+
if (!model)
|
|
4344
|
+
return false;
|
|
4345
|
+
if (kind === "cursor")
|
|
4346
|
+
return cursorModelIsFastVariant(model);
|
|
4347
|
+
// Grokのfast版は一覧の表示名で対にする(grokFastVariants)。起動の前は一覧を引かずに済むよう、IDの末尾で見る。
|
|
4348
|
+
if (kind === "grok")
|
|
4349
|
+
return /-fast$/.test(model);
|
|
4350
|
+
return false;
|
|
4351
|
+
}
|
|
4352
|
+
function claudeUserSettingsPath() {
|
|
4353
|
+
return path.join(claudeConfigDir(), "settings.json");
|
|
4354
|
+
}
|
|
4355
|
+
/** Claude Codeの席のmodelがfastへ切り替えられるかを、一覧で確かめる。使えない時は断る。 */
|
|
4356
|
+
async function assertClaudeFastSupported(bin, cwd, model, untouched) {
|
|
4357
|
+
const catalog = await claudeModelChoices(bin, cwd);
|
|
4358
|
+
const key = model ?? "default";
|
|
4359
|
+
if (!catalog.fast_ids?.[key]) {
|
|
4360
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Claude Code の ${key} はfastに対応していません(対応: ${Object.keys(catalog.fast_ids ?? {}).join(", ") || "なし"})。${untouched}`);
|
|
4361
|
+
}
|
|
4362
|
+
return catalog.fast_unavailable ?? null;
|
|
4363
|
+
}
|
|
4364
|
+
/** Codexの席のmodel(省略時は利用者のconfig、無ければ一覧の既定)がfastへ切り替えられるかを、一覧で確かめる。 */
|
|
4365
|
+
async function assertCodexFastSupported(bin, codexHome, model, untouched) {
|
|
4366
|
+
const catalog = await codexModelChoices(bin, true);
|
|
4367
|
+
const effective = model ?? readCodexConfigPins(path.join(codexHome, "config.toml")).model.value ?? catalog.default_model;
|
|
4368
|
+
if (!effective || !catalog.fast_ids?.[effective]) {
|
|
4369
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Codex の ${effective ?? "(modelを特定できません)"} はfastに対応していません(対応: ${Object.keys(catalog.fast_ids ?? {}).join(", ") || "なし"})。${untouched}`);
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
/** 起動の前の確かめ。設定の誤り(対応していないmodel、IDとの食い違い)はここで断り、席を作らない。 */
|
|
4373
|
+
async function planLaunchFast(kind, o) {
|
|
4374
|
+
const variant = modelIsFastVariant(kind, o.model);
|
|
4375
|
+
const plan = { model: o.model, cursor_fast: false, state: variant, switch_after_ready: false, fast_error: null };
|
|
4376
|
+
if (o.fast === false && variant) {
|
|
4377
|
+
throw new FastModeError("FAST_MODE_MODEL_CONFLICT", `model=${o.model} はfast版のIDですが、fast: false が渡されました。元のmodel IDを渡すか、fastを省いてください。起動していません。`);
|
|
4378
|
+
}
|
|
4379
|
+
if (o.fast !== true || variant)
|
|
4380
|
+
return plan;
|
|
4381
|
+
const untouched = "起動していません。";
|
|
4382
|
+
const bin = resolveAgentBin(kind);
|
|
4383
|
+
// CLIが無い時は、後の起動が同じ訳で断る。ここでは先へ進める。
|
|
4384
|
+
if (!bin)
|
|
4385
|
+
return plan;
|
|
4386
|
+
const cwd = o.cwd ?? process.cwd();
|
|
4387
|
+
if (kind === "claude") {
|
|
4388
|
+
plan.fast_error = await assertClaudeFastSupported(bin, cwd, o.model, untouched);
|
|
4389
|
+
plan.switch_after_ready = plan.fast_error === null;
|
|
4390
|
+
return plan;
|
|
4391
|
+
}
|
|
4392
|
+
if (kind === "codex") {
|
|
4393
|
+
await assertCodexFastSupported(bin, realCodexHome(), o.model, untouched);
|
|
4394
|
+
plan.switch_after_ready = true;
|
|
4395
|
+
return plan;
|
|
4396
|
+
}
|
|
4397
|
+
if (kind === "cursor") {
|
|
4398
|
+
if (!o.model) {
|
|
4399
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Cursor のfastはmodelごとです。modelを指定してください。${untouched}`);
|
|
4400
|
+
}
|
|
4401
|
+
assertCursorModelAvailable(bin, cwd, o.model, o.effort, true);
|
|
4402
|
+
return { ...plan, cursor_fast: true, state: true };
|
|
4403
|
+
}
|
|
4404
|
+
const base = o.model ?? GROK_MODEL_DEFAULTS[kind];
|
|
4405
|
+
const catalog = await grokCatalogForFast(bin, cwd, base);
|
|
4406
|
+
const fastId = catalog.fast_ids?.[base];
|
|
4407
|
+
if (!fastId) {
|
|
4408
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Grok の ${base} にfast版のmodelがありません(fast版があるmodel: ${Object.keys(catalog.fast_ids ?? {}).join(", ") || "なし"})。${untouched}`);
|
|
4409
|
+
}
|
|
4410
|
+
return { ...plan, model: fastId, state: true };
|
|
4411
|
+
}
|
|
4412
|
+
const USER_DEFAULT_SETTLE_MS = 400;
|
|
4413
|
+
const USER_DEFAULT_RESTORE_PASSES = 3;
|
|
4414
|
+
/**
|
|
4415
|
+
* harnessが利用者の既定へ書いた分を、切り替えの直前の中身(before)を元に戻す。
|
|
4416
|
+
* harnessは1回の切り替えで何度か書く事があるので(Grokの起動で見た)、少し置きながら何度か見て、後から書かれた分も戻す。
|
|
4417
|
+
*/
|
|
4418
|
+
async function restoreUserDefaults(restore) {
|
|
4419
|
+
for (let pass = 0; pass < USER_DEFAULT_RESTORE_PASSES; pass++) {
|
|
4420
|
+
await sleep(USER_DEFAULT_SETTLE_MS);
|
|
4421
|
+
try {
|
|
4422
|
+
restore();
|
|
4423
|
+
}
|
|
4424
|
+
catch { /* 戻せなくても、切り替えの結果は返す */ }
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
/** 設定fileを触る切り替えを鍵の中で流し、終わったら、harnessが書いた既定を切り替えの直前の値へ戻す。 */
|
|
4428
|
+
async function keepingUserDefaults(configPath, restore, action) {
|
|
4429
|
+
return withUserConfigLock(configPath, async () => {
|
|
4430
|
+
const before = readFileOrNull(configPath);
|
|
4431
|
+
try {
|
|
4432
|
+
return await action();
|
|
4433
|
+
}
|
|
4434
|
+
finally {
|
|
4435
|
+
await restoreUserDefaults(() => restore(before));
|
|
4436
|
+
}
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
/**
|
|
4440
|
+
* 起動の --model の値を利用者の既定へ書くharness(Grok 1.0.50。Cursorは、既定のmodelを設定に持つ端末)を起こす。
|
|
4441
|
+
* 鍵を取り、起動の直前の中身を読み、起こして、既定が書かれるのを待ってから、直前の値へ戻す。
|
|
4442
|
+
* - written: 起動の直前の中身と今の中身で、既定の項目が変わったか(harnessは起動時に、既定と関係の無い控えも書く。fileの変化だけでは見ない)。
|
|
4443
|
+
* - mustWrite: この起動で、既定が必ず書かれる見込みか。trueなら、入力待ちになっても書かれるまで待つ(遅い起動では、入力待ちの後に書かれる)。
|
|
4444
|
+
*/
|
|
4445
|
+
async function launchKeepingUserDefaults(configPath, o, launch) {
|
|
4446
|
+
return withUserConfigLock(configPath, async () => {
|
|
4447
|
+
const before = readFileOrNull(configPath);
|
|
4448
|
+
const mustWrite = o.mustWrite(before);
|
|
4449
|
+
const launched = launch();
|
|
4450
|
+
const started = performance.now();
|
|
4451
|
+
const deadline = started + (mustWrite ? LAUNCH_DEFAULT_MUST_WRITE_WAIT_MS : LAUNCH_DEFAULT_WAIT_MS);
|
|
4452
|
+
let aliveCheckedAt = started;
|
|
4453
|
+
do {
|
|
4454
|
+
await sleep(150);
|
|
4455
|
+
if (o.written(before, readFileOrNull(configPath)))
|
|
4456
|
+
break;
|
|
4457
|
+
if (!sessionExists(launched[0]))
|
|
4458
|
+
break;
|
|
4459
|
+
// 書かれる見込みが無い時は、入力待ちになった所で戻しへ進む(書かない端末・同じ値の時)。
|
|
4460
|
+
if (!mustWrite && o.ready(captureScreen(launched[0], AGENT_TUI_READY_LINES)))
|
|
4461
|
+
break;
|
|
4462
|
+
// harnessのprocessがもう居ない(起動に失敗した等)なら、書かれる事は無い。
|
|
4463
|
+
if (performance.now() - aliveCheckedAt >= LAUNCH_ALIVE_CHECK_MS) {
|
|
4464
|
+
aliveCheckedAt = performance.now();
|
|
4465
|
+
if (observeSession(launched[0]).harness_alive === false)
|
|
4466
|
+
break;
|
|
4467
|
+
}
|
|
4468
|
+
} while (performance.now() < deadline);
|
|
4469
|
+
await restoreUserDefaults(() => o.restore(before));
|
|
4470
|
+
return launched;
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
function parseJsonObject(body) {
|
|
4474
|
+
if (body === null)
|
|
4475
|
+
return null;
|
|
4476
|
+
try {
|
|
4477
|
+
const value = JSON.parse(body.toString("utf8"));
|
|
4478
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4479
|
+
}
|
|
4480
|
+
catch {
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
/** Cursorのcli-config.jsonで、既定のmodelの項目(CURSOR_DEFAULT_MODEL_KEYS)が、前の中身から変わったか。 */
|
|
4485
|
+
function cursorDefaultsWritten(before, now) {
|
|
4486
|
+
const previous = parseJsonObject(before), current = parseJsonObject(now);
|
|
4487
|
+
if (current === null)
|
|
4488
|
+
return false;
|
|
4489
|
+
return CURSOR_DEFAULT_MODEL_KEYS.some((key) => !isDeepStrictEqual(previous?.[key], current[key]));
|
|
4490
|
+
}
|
|
4491
|
+
/** Grokのconfig.tomlで、[models] の既定(default・default_reasoning_effort)が、前の中身から変わったか。 */
|
|
4492
|
+
function grokDefaultsWritten(before, now) {
|
|
4493
|
+
if (now === null)
|
|
4494
|
+
return false;
|
|
4495
|
+
const previous = grokModelDefaultsFromBody(before === null ? null : before.toString("utf8"));
|
|
4496
|
+
const current = grokModelDefaultsFromBody(now.toString("utf8"));
|
|
4497
|
+
return ["default", "default_reasoning_effort"].some((key) => previous[key].present !== current[key].present || previous[key].value !== current[key].value);
|
|
4498
|
+
}
|
|
4499
|
+
const grokUserConfigPath = (grokHome) => path.join(grokHome, "config.toml");
|
|
4500
|
+
const restoreGrokDefaults = (configPath) => (before) => restoreGrokModelDefaults(configPath, grokModelDefaultsFromBody(before === null ? null : before.toString("utf8")));
|
|
4501
|
+
const restoreCursorDefaults = (configPath) => (before) => {
|
|
4502
|
+
// 前はfileが無かった時は、Cursorが作った物をそのまま残す(既定を持たない端末に、消す項目は無い)。
|
|
4503
|
+
if (before !== null)
|
|
4504
|
+
restoreJsonSetting(configPath, before, CURSOR_DEFAULT_MODEL_KEYS);
|
|
4505
|
+
};
|
|
4506
|
+
function recordAgentFast(meta, fast) {
|
|
4507
|
+
meta.fast = fast;
|
|
4508
|
+
writeAgentMetadata(meta);
|
|
4509
|
+
}
|
|
4510
|
+
/** 動いている席のfastを切り替える。返りは切り替えた後の状態。席は入力待ちである事(呼び手が確かめる)。 */
|
|
4511
|
+
async function switchAgentFast(name, meta, target, verified = false) {
|
|
4512
|
+
const state = meta.kind === "claude" ? await switchClaudeFast(name, meta, target, verified)
|
|
4513
|
+
: meta.kind === "codex" ? await switchCodexFast(name, meta, target, verified)
|
|
4514
|
+
: meta.kind === "cursor" ? await switchCursorFast(name, meta, target)
|
|
4515
|
+
: await switchGrokFast(name, meta, target);
|
|
4516
|
+
recordAgentFast(meta, state);
|
|
4517
|
+
return state;
|
|
4518
|
+
}
|
|
4519
|
+
async function switchClaudeFast(name, meta, target, verified = false) {
|
|
4520
|
+
// もう通常の速度なら何も打たない(/fast off は、もう off でも利用者の設定を書き直す)。
|
|
4521
|
+
if (!target && meta.fast !== true && !claudeFastIndicatorShown(captureScreen(name, AGENT_TUI_READY_LINES)))
|
|
4522
|
+
return false;
|
|
4523
|
+
const untouched = "席の速度は変えていません。";
|
|
4524
|
+
if (target && !verified) {
|
|
4525
|
+
// 対応していないmodelで /fast on を打つと、Claude Codeは勝手にOpusへ替える。送る前に一覧で確かめる。
|
|
4526
|
+
const bin = meta.agent_executable ?? resolveAgentBin("claude");
|
|
4527
|
+
if (!bin)
|
|
4528
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Claude Code の CLI が見つからず、fastの対応を確かめられません。${untouched}`);
|
|
4529
|
+
if (meta.model === undefined) {
|
|
4530
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `この席のmodelが控えに無く(前の版のAitermが起こした席)、fastの対応を確かめられません。agent_configureへmodelも渡してください。${untouched}`);
|
|
4531
|
+
}
|
|
4532
|
+
const unavailable = await assertClaudeFastSupported(bin, meta.cwd ?? process.cwd(), meta.model, untouched);
|
|
4533
|
+
if (unavailable)
|
|
4534
|
+
throw new FastModeError("FAST_MODE_UNAVAILABLE", `${unavailable.message.replace(/^FAST_MODE_UNAVAILABLE: /, "")}${untouched}`);
|
|
4535
|
+
}
|
|
4536
|
+
const settings = claudeUserSettingsPath();
|
|
4537
|
+
// Claude Codeは結果の行を出す前に、利用者の設定へ fastMode を書く(on は true、off は項目を消す)。切り替えの前の値へ戻す。
|
|
4538
|
+
const seen = await keepingUserDefaults(settings, (before) => { restoreJsonSetting(settings, before, "fastMode"); }, async () => {
|
|
4539
|
+
const beforeScreen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
4540
|
+
await sendAgentPromptText(name, target ? "/fast on" : "/fast off");
|
|
4541
|
+
const deadline = performance.now() + CLAUDE_FAST_RESULT_TIMEOUT_MS;
|
|
4542
|
+
let result = null;
|
|
4543
|
+
do {
|
|
4544
|
+
await sleep(150);
|
|
4545
|
+
result = claudeFastResult(beforeScreen, captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4546
|
+
} while (!result && performance.now() < deadline);
|
|
4547
|
+
return result;
|
|
4548
|
+
});
|
|
4549
|
+
await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4550
|
+
if (!seen) {
|
|
4551
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Claude Codeへ ${target ? "/fast on" : "/fast off"} を送りましたが、結果の行を確かめられません。席の速度は変わった可能性があります。`);
|
|
4552
|
+
}
|
|
4553
|
+
if (seen.state === "unavailable")
|
|
4554
|
+
throw new FastModeError("FAST_MODE_UNAVAILABLE", `Claude Code: ${seen.line}。${untouched}`);
|
|
4555
|
+
if (seen.state === "unchanged")
|
|
4556
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Claude Code: ${seen.line}`);
|
|
4557
|
+
if ((seen.state === "on") !== target) {
|
|
4558
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Claude Codeの結果の行が頼んだ向きと違います(${seen.line})。`);
|
|
4559
|
+
}
|
|
4560
|
+
return target;
|
|
4561
|
+
}
|
|
4562
|
+
/** Codexの入力欄で `/fast` を1回打つ(入り切りの交互)。返りは、切り替えた後にfastかどうか。 */
|
|
4563
|
+
async function toggleCodexFast(name) {
|
|
4564
|
+
const before = codexServiceTierNotices(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4565
|
+
send(name, "/fast", { enter: false, force: true, raw: false, mark: false, rtk: false, preserveAgentOperation: true, bracketedPaste: true });
|
|
4566
|
+
// 命令の候補に /fast が出ていない時にEnterを押すと、文として送られて番が始まる。出るのを見てから押す。
|
|
4567
|
+
let offered = false;
|
|
4568
|
+
for (const deadline = performance.now() + CODEX_FAST_OFFER_TIMEOUT_MS; performance.now() < deadline; await sleep(100)) {
|
|
4569
|
+
if (codexFastCommandOffered(captureScreen(name, AGENT_TUI_READY_LINES))) {
|
|
4570
|
+
offered = true;
|
|
4571
|
+
break;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
if (!offered) {
|
|
4575
|
+
for (let i = 0; i < "/fast".length; i++)
|
|
4576
|
+
sendMenuChoice(name, "BSpace");
|
|
4577
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", "Codexの入力欄に /fast の命令が出ません(このmodel・この設定では速度の段を切り替えられません)。席の速度は変えていません。");
|
|
4578
|
+
}
|
|
4579
|
+
sendKey(name, "Enter", { preserveAgentOperation: true });
|
|
4580
|
+
for (const deadline = performance.now() + CODEX_FAST_NOTICE_TIMEOUT_MS; performance.now() < deadline; await sleep(120)) {
|
|
4581
|
+
const now = codexServiceTierNotices(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4582
|
+
if (now.length > before.length || (now.length > 0 && now.at(-1) !== before.at(-1)))
|
|
4583
|
+
return now.at(-1) === "fast";
|
|
4584
|
+
}
|
|
4585
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Codexへ /fast を送りましたが、切り替えた後の「Service tier set to …」の行を確かめられません。席の速度は変わった可能性があります。");
|
|
4586
|
+
}
|
|
4587
|
+
async function switchCodexFast(name, meta, target, verified = false) {
|
|
4588
|
+
// 席の控えが、Aitermの知る今の状態。足元に fast と出ている時(本物のログインの席は「GPT-5.6-Sol xhigh fast · …」と出す)は、
|
|
4589
|
+
// 控えより画面を信じる(席で人が切り替えた時に、fastのまま「通常」と返さない)。出ていない時は、通常か、段を出さない構成かを区別できない。
|
|
4590
|
+
const shownFast = codexFooterShowsFast(captureScreen(name, AGENT_TUI_READY_LINES)) === true;
|
|
4591
|
+
if (shownFast && target)
|
|
4592
|
+
return true;
|
|
4593
|
+
if (meta.fast === target && !shownFast)
|
|
4594
|
+
return target;
|
|
4595
|
+
const bin = meta.agent_executable ?? resolveAgentBin("codex");
|
|
4596
|
+
const codexHome = meta.codex_home ?? realCodexHome();
|
|
4597
|
+
if (target && !verified) {
|
|
4598
|
+
if (!bin)
|
|
4599
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Codex の CLI が見つからず、fastの対応を確かめられません。席の速度は変えていません。");
|
|
4600
|
+
await assertCodexFastSupported(bin, codexHome, meta.model ?? null, "席の速度は変えていません。");
|
|
4601
|
+
}
|
|
4602
|
+
// /fast は切り替えのたびに、利用者のconfig.tomlへ service_tier を保存する。切り替えの直前の値へ戻す。
|
|
4603
|
+
return keepingCodexDefaults(bin, codexHome, ["service_tier"], async () => {
|
|
4604
|
+
let state = await toggleCodexFast(name);
|
|
4605
|
+
// 控えと実際が違っていた時(席で人が切り替えた等)は、1回で逆へ動く。もう1回打って頼まれた向きにする。
|
|
4606
|
+
if (state !== target)
|
|
4607
|
+
state = await toggleCodexFast(name);
|
|
4608
|
+
if (state !== target) {
|
|
4609
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Codexの /fast を2回打っても、頼まれた向きになりません。");
|
|
4610
|
+
}
|
|
4611
|
+
return target;
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
/**
|
|
4615
|
+
* Codexの席の中の切り替え(/fast、/model の選択)を鍵の中で流し、Codexが利用者のconfig.tomlへ保存した項目を、切り替えの直前の値へ戻す。
|
|
4616
|
+
* 戻しは公式の口(App Serverの config/batchWrite)。戻せない時と、戻した後もfileが前の値でない時は、その1行だけをfileで直す
|
|
4617
|
+
* (戻さずに残すと、利用者の次のCodexが、席の値で始まる)。
|
|
4618
|
+
*/
|
|
4619
|
+
async function keepingCodexDefaults(bin, codexHome, keys, action) {
|
|
4620
|
+
const configPath = path.join(codexHome, "config.toml");
|
|
4621
|
+
const pinKey = { service_tier: "service_tier", model: "model", model_reasoning_effort: "effort" };
|
|
4622
|
+
return withUserConfigLock(configPath, async () => {
|
|
4623
|
+
const pins = readCodexConfigPins(configPath);
|
|
4624
|
+
const before = Object.fromEntries(keys.map((key) => [key, pins[pinKey[key]]]));
|
|
4625
|
+
const fixInFile = () => {
|
|
4626
|
+
const now = readCodexConfigPins(configPath);
|
|
4627
|
+
for (const key of keys) {
|
|
4628
|
+
const pin = now[pinKey[key]];
|
|
4629
|
+
if (pin.present === before[key].present && pin.value === before[key].value)
|
|
4630
|
+
continue;
|
|
4631
|
+
try {
|
|
4632
|
+
restoreCodexDefaultInFile(configPath, key, before[key]);
|
|
4633
|
+
}
|
|
4634
|
+
catch { /* 戻せなくても、切り替えの結果は返す */ }
|
|
4635
|
+
}
|
|
4636
|
+
};
|
|
4637
|
+
try {
|
|
4638
|
+
return await action();
|
|
4639
|
+
}
|
|
4640
|
+
finally {
|
|
4641
|
+
// Codexは切り替えの知らせを出した後に保存する。保存を待ってから戻す。
|
|
4642
|
+
await sleep(USER_DEFAULT_SETTLE_MS);
|
|
4643
|
+
try {
|
|
4644
|
+
if (!bin)
|
|
4645
|
+
throw new AitermError("Codex の CLI が見つかりません", 2);
|
|
4646
|
+
await restoreCodexDefaults(bin, codexHome, before);
|
|
4647
|
+
}
|
|
4648
|
+
catch { /* 下で、fileのその行を直す */ }
|
|
4649
|
+
fixInFile();
|
|
4650
|
+
await sleep(USER_DEFAULT_SETTLE_MS);
|
|
4651
|
+
fixInFile();
|
|
4652
|
+
}
|
|
4653
|
+
});
|
|
4654
|
+
}
|
|
4655
|
+
const CURSOR_PICKER_CLOSE_MS = 600;
|
|
4656
|
+
/**
|
|
4657
|
+
* Cursorの `/model` の選ぶ画面を閉じた後、入力欄を描き直させる。
|
|
4658
|
+
* 2026.10.01のCursorは、選ぶ画面を閉じても入力欄の行を描かず(選ぶ画面の「Filter:」の行が残る)、鍵が1つ入るまでそのまま。
|
|
4659
|
+
* そのままだとAitermは入力待ちと読めず、次の送信を断る。空白を1つ打って消すと、入力欄が描かれる(入力欄は空のまま)。
|
|
4660
|
+
*/
|
|
4661
|
+
async function redrawCursorComposer(name) {
|
|
4662
|
+
await sleep(CURSOR_PICKER_CLOSE_MS);
|
|
4663
|
+
sendMenuChoice(name, "Space");
|
|
4664
|
+
await sleep(150);
|
|
4665
|
+
sendMenuChoice(name, "BSpace");
|
|
4666
|
+
await sleep(150);
|
|
4667
|
+
}
|
|
4668
|
+
/** Cursorの `/model` の画面が、その姿(list=選ぶ画面、parameters=パラメータの画面、null=閉じた)になるのを待つ。 */
|
|
4669
|
+
async function waitCursorPickerView(name, view, timeoutMs = 4_000) {
|
|
4670
|
+
const deadline = performance.now() + timeoutMs;
|
|
4671
|
+
do {
|
|
4672
|
+
const screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
4673
|
+
if (cursorPickerView(screen) === view)
|
|
4674
|
+
return screen;
|
|
4675
|
+
await sleep(100);
|
|
4676
|
+
} while (performance.now() < deadline);
|
|
4677
|
+
throw new AitermError(`Cursor の /model の画面(${view === "list" ? "選ぶ画面" : view === "parameters" ? "Edit Parameters" : "閉じた後"})を確認できません`, 2);
|
|
4678
|
+
}
|
|
4679
|
+
const CURSOR_PARAMETER_STEP_TIMEOUT_MS = 800;
|
|
4680
|
+
/**
|
|
4681
|
+
* Cursorのパラメータの画面で、選択中(→)の行を、目当ての名前の行まで動かす。
|
|
4682
|
+
* 矢印を続けて送ると鍵が落ちる事があり(2026.10.01で見た)、違う行でEnterを押すと別の値を選んでしまう。
|
|
4683
|
+
* 1歩ずつ、画面で矢印が動いたのを確かめる。目当ての行に着いた時だけtrueを返す。
|
|
4684
|
+
*/
|
|
4685
|
+
async function moveCursorParameterTo(name, label) {
|
|
4686
|
+
for (let step = 0; step < 24; step++) {
|
|
4687
|
+
const rows = cursorParameterRows(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4688
|
+
const at = rows.findIndex((row) => row.cursor);
|
|
4689
|
+
const target = rows.findIndex((row) => row.label === label);
|
|
4690
|
+
if (at < 0 || target < 0)
|
|
4691
|
+
return false;
|
|
4692
|
+
if (at === target)
|
|
4693
|
+
return true;
|
|
4694
|
+
sendMenuChoice(name, target > at ? "Down" : "Up");
|
|
4695
|
+
for (const deadline = performance.now() + CURSOR_PARAMETER_STEP_TIMEOUT_MS; performance.now() < deadline; await sleep(60)) {
|
|
4696
|
+
if (cursorParameterRows(captureScreen(name, AGENT_TUI_READY_LINES)).findIndex((row) => row.cursor) !== at)
|
|
4697
|
+
break;
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
return false;
|
|
4701
|
+
}
|
|
4702
|
+
/** Cursorの `/model` の操作を流し、Cursorが利用者の既定(cli-config.jsonのmodelの項目)へ書いた分を、直前の値へ戻す。 */
|
|
4703
|
+
function keepingCursorDefaults(meta, action) {
|
|
4704
|
+
const configPath = cursorUserConfigPath(meta.cursor_home ?? realCursorHome());
|
|
4705
|
+
return keepingUserDefaults(configPath, restoreCursorDefaults(configPath), action);
|
|
4706
|
+
}
|
|
4707
|
+
async function switchCursorFast(name, meta, target) {
|
|
4708
|
+
// 見出しのmodelの表示が、もう頼まれた向きなら、選ぶ画面を開かない(設定替えのたびに fast: false を渡す呼び手がいる)。
|
|
4709
|
+
if (cursorHeaderShowsFast(captureScreen(name, AGENT_TUI_READY_LINES)) === target)
|
|
4710
|
+
return target;
|
|
4711
|
+
return keepingCursorDefaults(meta, () => toggleCursorFast(name, meta, target));
|
|
4712
|
+
}
|
|
4713
|
+
async function toggleCursorFast(name, meta, target) {
|
|
4714
|
+
// 引数なしの /model は、今のmodelの行に印を付けて開く。Tabでそのmodelのパラメータの画面(Edit Parameters)。
|
|
4715
|
+
await sendAgentPromptText(name, "/model", meta.kind);
|
|
4716
|
+
await waitCursorPickerView(name, "list");
|
|
4717
|
+
sendMenuChoice(name, "Tab");
|
|
4718
|
+
/** 選ばずに閉じる。パラメータの画面ならEscで選ぶ画面へ戻り、もう一度Escで閉じる。 */
|
|
4719
|
+
const closeWithoutChange = async () => {
|
|
4720
|
+
for (let i = 0; i < 2 && cursorPickerView(captureScreen(name, AGENT_TUI_READY_LINES)) !== null; i++) {
|
|
4721
|
+
const view = cursorPickerView(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4722
|
+
sendMenuChoice(name, "Escape");
|
|
4723
|
+
await waitCursorPickerView(name, view === "parameters" ? "list" : null).catch(() => undefined);
|
|
4724
|
+
}
|
|
4725
|
+
await redrawCursorComposer(name);
|
|
4726
|
+
await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4727
|
+
};
|
|
4728
|
+
let parameters;
|
|
4729
|
+
try {
|
|
4730
|
+
parameters = await waitCursorPickerView(name, "parameters");
|
|
4731
|
+
}
|
|
4732
|
+
catch {
|
|
4733
|
+
await closeWithoutChange();
|
|
4734
|
+
if (target)
|
|
4735
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", "Cursorの今のmodelには、パラメータの画面がありません。席の速度は変えていません。");
|
|
4736
|
+
return false;
|
|
4737
|
+
}
|
|
4738
|
+
const row = cursorFastParameter(parameters);
|
|
4739
|
+
if (!row) {
|
|
4740
|
+
await closeWithoutChange();
|
|
4741
|
+
if (target)
|
|
4742
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", "Cursorの今のmodelのパラメータに、Fastの行がありません。席の速度は変えていません。");
|
|
4743
|
+
return false;
|
|
4744
|
+
}
|
|
4745
|
+
if (row.state === target) {
|
|
4746
|
+
await closeWithoutChange();
|
|
4747
|
+
return target;
|
|
4748
|
+
}
|
|
4749
|
+
if (!(await moveCursorParameterTo(name, "Fast"))) {
|
|
4750
|
+
await closeWithoutChange();
|
|
4751
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Cursorのパラメータの画面で、Fastの行へ動かせません。選ばずに閉じました。席の速度は変えていません。");
|
|
4752
|
+
}
|
|
4753
|
+
sendMenuChoice(name, "Enter");
|
|
4754
|
+
let now = row;
|
|
4755
|
+
for (const deadline = performance.now() + CURSOR_FAST_ROW_TIMEOUT_MS; performance.now() < deadline; await sleep(100)) {
|
|
4756
|
+
now = cursorFastParameter(captureScreen(name, AGENT_TUI_READY_LINES)) ?? now;
|
|
4757
|
+
if (now.state === target)
|
|
4758
|
+
break;
|
|
4759
|
+
}
|
|
4760
|
+
if (now.state !== target) {
|
|
4761
|
+
await closeWithoutChange();
|
|
4762
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Cursorのパラメータの画面で、Fastの行の印が替わりません。選ばずに閉じました。");
|
|
4763
|
+
}
|
|
4764
|
+
sendMenuChoice(name, "Escape");
|
|
4765
|
+
await waitCursorPickerView(name, "list");
|
|
4766
|
+
sendMenuChoice(name, "Enter");
|
|
4767
|
+
await waitCursorPickerView(name, null);
|
|
4768
|
+
await redrawCursorComposer(name);
|
|
4769
|
+
const ready = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4770
|
+
const shown = cursorHeaderShowsFast(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4771
|
+
if (!ready.ready || (shown !== null && shown !== target)) {
|
|
4772
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Cursorの見出しのmodelの表示で、切り替えた後の状態を確かめられません。席の速度は変わった可能性があります。");
|
|
4773
|
+
}
|
|
4774
|
+
return target;
|
|
4775
|
+
}
|
|
4776
|
+
/** Grokの `/model …`(`/effort …`)を打ち、Grokが利用者の既定([models] の default・default_reasoning_effort)へ書いた分を、切り替えの直前の値へ戻す。 */
|
|
4777
|
+
async function sendGrokModelCommand(name, meta, command, model, effort) {
|
|
4778
|
+
const configPath = grokUserConfigPath(meta.grok_home ?? realGrokHome());
|
|
4779
|
+
await keepingUserDefaults(configPath, restoreGrokDefaults(configPath), async () => {
|
|
4780
|
+
const before = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
4781
|
+
await sendAgentPromptText(name, command);
|
|
4782
|
+
// modelの替えは、端末によって知らせが出るまで数秒かかる(Windowsで3秒を超えた)。
|
|
4783
|
+
await waitForGrokConfigurationResult(name, before, model, effort, model ? GROK_MODEL_SWITCH_TIMEOUT_MS : undefined);
|
|
4784
|
+
});
|
|
4785
|
+
}
|
|
4786
|
+
async function switchGrokFast(name, meta, target) {
|
|
4787
|
+
const untouched = "席の速度は変えていません。";
|
|
4788
|
+
// 足元のmodelの表示(「Grok 4.7 Fast (high)」)が、もう頼まれた向きなら、一覧を引かない。
|
|
4789
|
+
const shown = grokFooterModelName(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4790
|
+
if (shown !== null && / Fast$/.test(shown) === target)
|
|
4791
|
+
return target;
|
|
4792
|
+
const bin = meta.agent_executable ?? resolveAgentBin(meta.kind);
|
|
4793
|
+
if (!bin)
|
|
4794
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Grok の CLI が見つからず、fastの対応を確かめられません。${untouched}`);
|
|
4795
|
+
let catalog = await grokModelChoices(bin, meta.cwd ?? process.cwd());
|
|
4796
|
+
let current = grokModelFromFooter(captureScreen(name, AGENT_TUI_READY_LINES), catalog);
|
|
4797
|
+
if (!current || (target && !catalog.fast_ids?.[current.base])) {
|
|
4798
|
+
// 一覧にfast版が見えない時(今のmodelがfast版だと、足元の表示も一覧と合わない)は、1度聞き直す。
|
|
4799
|
+
await sleep(GROK_FAST_CATALOG_RETRY_MS);
|
|
4800
|
+
catalog = await grokModelChoices(bin, meta.cwd ?? process.cwd());
|
|
4801
|
+
current = grokModelFromFooter(captureScreen(name, AGENT_TUI_READY_LINES), catalog);
|
|
4802
|
+
}
|
|
4803
|
+
if (!current)
|
|
4804
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Grokの足元から、今のmodelを読めません。${untouched}`);
|
|
4805
|
+
if (current.fast === target)
|
|
4806
|
+
return target;
|
|
4807
|
+
const next = target ? catalog.fast_ids?.[current.base] : current.base;
|
|
4808
|
+
if (!next) {
|
|
4809
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Grok の ${current.base} にfast版のmodelがありません(fast版があるmodel: ${Object.keys(catalog.fast_ids ?? {}).join(", ") || "なし"})。${untouched}`);
|
|
4810
|
+
}
|
|
4811
|
+
// 切り替えの知らせを取り逃しても、足元のmodelの表示が頼んだ向きになっていれば、切り替わっている。
|
|
4812
|
+
let noticeError = null;
|
|
4813
|
+
try {
|
|
4814
|
+
await sendGrokModelCommand(name, meta, `/model ${next}`, next, null);
|
|
4815
|
+
}
|
|
4816
|
+
catch (error) {
|
|
4817
|
+
noticeError = error;
|
|
4818
|
+
}
|
|
4819
|
+
const after = grokModelFromFooter(captureScreen(name, AGENT_TUI_READY_LINES), catalog);
|
|
4820
|
+
if (after?.fast === target)
|
|
4821
|
+
return target;
|
|
4822
|
+
if (noticeError && after?.fast === current.fast) {
|
|
4823
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `Grokが /model ${next} を受け付けたのを確かめられません(${noticeError instanceof Error ? noticeError.message : String(noticeError)})。足元のmodelの表示は前のままです。`);
|
|
4824
|
+
}
|
|
4825
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", "Grokの足元のmodelの表示で、切り替えた後の状態を確かめられません。席の速度は変わった可能性があります。");
|
|
4826
|
+
}
|
|
4827
|
+
/** 起動の後、入力待ちになった席をfastへ切り替える。出来なかった訳は返りへ入れ、起動は続ける。 */
|
|
4828
|
+
async function applyLaunchFast(name, meta) {
|
|
4829
|
+
try {
|
|
4830
|
+
// 対応は、起動の前(planLaunchFast)に一覧で確かめてある。
|
|
4831
|
+
await switchAgentFast(name, meta, true, true);
|
|
4832
|
+
return null;
|
|
4833
|
+
}
|
|
4834
|
+
catch (error) {
|
|
4835
|
+
const failure = fastModeFailure(error);
|
|
4836
|
+
if (failure)
|
|
4837
|
+
return failure;
|
|
4838
|
+
return {
|
|
4839
|
+
code: "FAST_MODE_UNCONFIRMED",
|
|
4840
|
+
message: `FAST_MODE_UNCONFIRMED: 起動の後の切り替えで失敗しました: ${error instanceof Error ? error.message : String(error)}`,
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4283
4844
|
export async function configureAgent(name, opts) {
|
|
4284
4845
|
assertSessionName(name);
|
|
4285
4846
|
const model = opts.model?.trim() || null;
|
|
4286
4847
|
const effort = opts.reasoning_effort?.trim() || null;
|
|
4287
|
-
|
|
4288
|
-
|
|
4848
|
+
const fast = opts.fast ?? null;
|
|
4849
|
+
if (!model && !effort && fast === null)
|
|
4850
|
+
throw new AitermError("model、reasoning_effort、fast のいずれかを指定してください", 2);
|
|
4289
4851
|
if ((model && /[\r\n]/.test(model)) || (effort && /[\r\n]/.test(effort))) {
|
|
4290
4852
|
throw new AitermError("model/reasoning_effort に改行を含められません", 2);
|
|
4291
4853
|
}
|
|
4292
4854
|
const meta = loadAgentMetadata(name);
|
|
4855
|
+
if (fast === false && modelIsFastVariant(meta.kind, model)) {
|
|
4856
|
+
throw new FastModeError("FAST_MODE_MODEL_CONFLICT", `model=${model} はfast版のIDですが、fast: false が渡されました。元のmodel IDを渡すか、fastを省いてください。設定は変えていません。`);
|
|
4857
|
+
}
|
|
4293
4858
|
bindCompletedInitialPrompt(meta);
|
|
4294
4859
|
const { ready, codexRateLimitModelSwitch } = await waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4295
4860
|
if (!ready.ready) {
|
|
@@ -4298,6 +4863,53 @@ export async function configureAgent(name, opts) {
|
|
|
4298
4863
|
}
|
|
4299
4864
|
throw new AitermError(`agent session '${name}' は入力待ちではありません`, 2);
|
|
4300
4865
|
}
|
|
4866
|
+
const configured = () => ({
|
|
4867
|
+
schema: "aiterm.agent-configure-result.v1",
|
|
4868
|
+
session_id: name,
|
|
4869
|
+
provider: meta.kind,
|
|
4870
|
+
harness: agentHarness(meta.kind),
|
|
4871
|
+
model,
|
|
4872
|
+
reasoning_effort: effort,
|
|
4873
|
+
fast: typeof meta.fast === "boolean" ? meta.fast : null,
|
|
4874
|
+
});
|
|
4875
|
+
const rememberModel = () => {
|
|
4876
|
+
if (!model)
|
|
4877
|
+
return;
|
|
4878
|
+
meta.model = model;
|
|
4879
|
+
writeAgentMetadata(meta);
|
|
4880
|
+
};
|
|
4881
|
+
// fast: true は、modelやeffortを替える前に対応を確かめる(断る時は何も変えない)。
|
|
4882
|
+
let fastVerified = false;
|
|
4883
|
+
if (fast === true && (meta.kind === "claude" || meta.kind === "codex")) {
|
|
4884
|
+
const untouched = "設定は変えていません。";
|
|
4885
|
+
const bin = meta.agent_executable ?? resolveAgentBin(meta.kind);
|
|
4886
|
+
if (!bin)
|
|
4887
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `${agentLabel(meta.kind)} の CLI が見つからず、fastの対応を確かめられません。${untouched}`);
|
|
4888
|
+
if (meta.kind === "claude") {
|
|
4889
|
+
const target = model ?? meta.model;
|
|
4890
|
+
if (target === undefined) {
|
|
4891
|
+
throw new FastModeError("FAST_MODE_UNCONFIRMED", `この席のmodelが控えに無く(前の版のAitermが起こした席)、fastの対応を確かめられません。modelも渡してください。${untouched}`);
|
|
4892
|
+
}
|
|
4893
|
+
const unavailable = await assertClaudeFastSupported(bin, meta.cwd ?? process.cwd(), target, untouched);
|
|
4894
|
+
if (unavailable)
|
|
4895
|
+
throw new FastModeError("FAST_MODE_UNAVAILABLE", `${unavailable.message.replace(/^FAST_MODE_UNAVAILABLE: /, "")}${untouched}`);
|
|
4896
|
+
}
|
|
4897
|
+
else {
|
|
4898
|
+
await assertCodexFastSupported(bin, meta.codex_home ?? realCodexHome(), model ?? meta.model ?? null, untouched);
|
|
4899
|
+
}
|
|
4900
|
+
fastVerified = true;
|
|
4901
|
+
}
|
|
4902
|
+
/** modelを替えた後、fastを省いた時: harnessは替えた先がfastに対応していなければfastを外す。席の控えを実際へ合わせる。 */
|
|
4903
|
+
const dropFastIfUnsupported = async () => {
|
|
4904
|
+
if (fast !== null || !model || meta.fast !== true)
|
|
4905
|
+
return;
|
|
4906
|
+
const bin = meta.agent_executable ?? resolveAgentBin(meta.kind);
|
|
4907
|
+
if (!bin)
|
|
4908
|
+
return;
|
|
4909
|
+
const catalog = meta.kind === "claude" ? await claudeModelChoices(bin, meta.cwd ?? process.cwd()) : await codexModelChoices(bin, true);
|
|
4910
|
+
if (!catalog.fast_ids?.[model])
|
|
4911
|
+
recordAgentFast(meta, false);
|
|
4912
|
+
};
|
|
4301
4913
|
if (meta.kind === "claude") {
|
|
4302
4914
|
// ultracodeはClaude Codeが自分で「this session only」と返すsession設定で、選ぶ画面には出ない。
|
|
4303
4915
|
const level = effort && CLAUDE_EFFORTS.has(effort) ? effort : null;
|
|
@@ -4312,78 +4924,139 @@ export async function configureAgent(name, opts) {
|
|
|
4312
4924
|
if (!effortReady.ready)
|
|
4313
4925
|
throw new AitermError(`Claudeのeffort変更完了を確認できません`, 2);
|
|
4314
4926
|
}
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
reasoning_effort: effort,
|
|
4322
|
-
};
|
|
4927
|
+
rememberModel();
|
|
4928
|
+
if (fast !== null)
|
|
4929
|
+
recordAgentFast(meta, await switchClaudeFast(name, meta, fast, fastVerified));
|
|
4930
|
+
else
|
|
4931
|
+
await dropFastIfUnsupported();
|
|
4932
|
+
return configured();
|
|
4323
4933
|
}
|
|
4324
4934
|
if (meta.kind === "cursor") {
|
|
4325
4935
|
validateCursorModelEffort(model, effort);
|
|
4326
|
-
if (!model)
|
|
4936
|
+
if (!model && effort)
|
|
4327
4937
|
throw new AitermError("Cursorのreasoning_effort変更にはmodelも指定してください", 2);
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4938
|
+
if (model) {
|
|
4939
|
+
const bin = resolveAgentBin("cursor");
|
|
4940
|
+
if (!bin)
|
|
4941
|
+
throw new AitermError("Cursor Agent CLI の CLI が見つかりません", 2);
|
|
4942
|
+
assertCursorModelAvailable(bin, meta.cwd ?? process.cwd(), model, effort);
|
|
4943
|
+
if (fast === true && !modelIsFastVariant("cursor", model)) {
|
|
4944
|
+
// そのmodelのどの組にもfast版が無ければ、modelを替える前に断る(組ごとの有無は、替えた後のパラメータの画面で分かる)。
|
|
4945
|
+
const fastIds = Object.keys(cursorModelChoices(bin, meta.cwd ?? process.cwd()).fast_ids ?? {});
|
|
4946
|
+
if (!fastIds.some((key) => key === model || key.startsWith(`${model}-`))) {
|
|
4947
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Cursor の ${model} にfast版がありません。設定は変えていません。`);
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4332
4951
|
// Cursor TUIの `/model <base model>` はmodelを直接切り替える一方、flatten済みcatalog ID
|
|
4333
4952
|
// (例: gpt-5.6-luna-high)を渡すとno matchesのfilter画面へ入る。effortは標準model pickerの
|
|
4334
4953
|
// parameter editorで選び、同じsessionと会話contextを保つ。
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
sendMenuChoice(name, "
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4954
|
+
// Cursorは選んだmodelとパラメータを、利用者の既定(cli-config.json)へも書く版がある。席だけの替えにするため、直前の値へ戻す。
|
|
4955
|
+
if (model)
|
|
4956
|
+
await keepingCursorDefaults(meta, async () => {
|
|
4957
|
+
await sendAgentPromptText(name, `/model ${model}`, meta.kind);
|
|
4958
|
+
const modelReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4959
|
+
if (!modelReady.ready)
|
|
4960
|
+
throw new AitermError("Cursorのmodel変更完了を確認できません", 2);
|
|
4961
|
+
if (!effort)
|
|
4962
|
+
return;
|
|
4963
|
+
await sendAgentPromptText(name, "/model", meta.kind);
|
|
4964
|
+
await waitCursorPickerView(name, "list");
|
|
4965
|
+
sendMenuChoice(name, "Tab");
|
|
4966
|
+
const parameterScreen = await waitCursorPickerView(name, "parameters");
|
|
4967
|
+
const navigation = cursorEffortNavigation(parameterScreen, effort);
|
|
4968
|
+
if (!(await moveCursorParameterTo(name, navigation.label))) {
|
|
4969
|
+
sendMenuChoice(name, "Escape");
|
|
4970
|
+
await waitCursorPickerView(name, "list").catch(() => undefined);
|
|
4971
|
+
sendMenuChoice(name, "Escape");
|
|
4972
|
+
await redrawCursorComposer(name);
|
|
4973
|
+
throw new AitermError(`Cursorのパラメータの画面で ${navigation.label} の行へ動かせません。reasoning effortは選んでいません。`, 2);
|
|
4974
|
+
}
|
|
4975
|
+
sendMenuChoice(name, "Enter");
|
|
4976
|
+
let selected = false;
|
|
4977
|
+
for (const deadline = performance.now() + CURSOR_FAST_ROW_TIMEOUT_MS; !selected && performance.now() < deadline; await sleep(100)) {
|
|
4978
|
+
selected = cursorEffortSelected(captureScreen(name, AGENT_TUI_READY_LINES), navigation.label);
|
|
4979
|
+
}
|
|
4980
|
+
if (!selected)
|
|
4981
|
+
throw new AitermError(`Cursor の ${navigation.label} ✓ 画面を確認できません`, 2);
|
|
4982
|
+
sendMenuChoice(name, "Escape");
|
|
4983
|
+
await waitCursorPickerView(name, "list");
|
|
4984
|
+
sendMenuChoice(name, "Enter");
|
|
4985
|
+
await waitCursorPickerView(name, null);
|
|
4986
|
+
await redrawCursorComposer(name);
|
|
4987
|
+
const effortReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
4988
|
+
if (!effortReady.ready)
|
|
4989
|
+
throw new AitermError("Cursorのreasoning effort変更完了を確認できません", 2);
|
|
4990
|
+
});
|
|
4991
|
+
rememberModel();
|
|
4992
|
+
if (fast !== null)
|
|
4993
|
+
recordAgentFast(meta, await switchCursorFast(name, meta, fast));
|
|
4994
|
+
else if (model) {
|
|
4995
|
+
// modelを替えた後の実際の状態を、見出しのmodelの表示で読む。
|
|
4996
|
+
const shown = cursorHeaderShowsFast(captureScreen(name, AGENT_TUI_READY_LINES));
|
|
4997
|
+
if (shown !== null)
|
|
4998
|
+
recordAgentFast(meta, shown);
|
|
4999
|
+
}
|
|
5000
|
+
return configured();
|
|
4364
5001
|
}
|
|
4365
5002
|
if (meta.kind === "grok") {
|
|
5003
|
+
// Grokのfastは別のmodel ID。modelを替える時は、頼まれた(省略なら席の今の)fastに合わせて、渡すIDを決める。
|
|
5004
|
+
let next = model;
|
|
4366
5005
|
if (model) {
|
|
4367
5006
|
const bin = resolveAgentBin(meta.kind);
|
|
4368
5007
|
if (!bin)
|
|
4369
5008
|
throw new AitermError(`${agentLabel(meta.kind)} の CLI が見つかりません`, 2);
|
|
4370
5009
|
assertGrokModelAvailable(bin, meta.cwd ?? process.cwd(), model);
|
|
5010
|
+
if ((fast ?? (meta.fast === true)) && !modelIsFastVariant("grok", model)) {
|
|
5011
|
+
const fastIds = (await grokCatalogForFast(bin, meta.cwd ?? process.cwd(), model)).fast_ids ?? {};
|
|
5012
|
+
if (fastIds[model])
|
|
5013
|
+
next = fastIds[model];
|
|
5014
|
+
else if (fast === true) {
|
|
5015
|
+
throw new FastModeError("FAST_MODE_NOT_SUPPORTED", `Grok の ${model} にfast版のmodelがありません(fast版があるmodel: ${Object.keys(fastIds).join(", ") || "なし"})。設定は変えていません。`);
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
4371
5018
|
}
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
: `/effort ${effort}
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
5019
|
+
if (model || effort) {
|
|
5020
|
+
// `/model` はGrokが利用者の既定([models].default)へも書くので、切り替えの直前の値へ戻す。
|
|
5021
|
+
await sendGrokModelCommand(name, meta, next ? `/model ${next}${effort ? ` ${effort}` : ""}` : `/effort ${effort}`, next, effort);
|
|
5022
|
+
if (model) {
|
|
5023
|
+
rememberModel();
|
|
5024
|
+
recordAgentFast(meta, next !== model || modelIsFastVariant("grok", model));
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
if (!model && fast !== null)
|
|
5028
|
+
recordAgentFast(meta, await switchGrokFast(name, meta, fast));
|
|
5029
|
+
return configured();
|
|
5030
|
+
}
|
|
5031
|
+
// `/model` の選択は、利用者のconfig.tomlへ model と model_reasoning_effort を既定として保存する。席だけの替えにするため、直前の値へ戻す。
|
|
5032
|
+
if (model || effort) {
|
|
5033
|
+
await keepingCodexDefaults(meta.agent_executable ?? resolveAgentBin("codex"), meta.codex_home ?? realCodexHome(), ["model", "model_reasoning_effort"], () => configureCodexModel(name, model, effort));
|
|
4386
5034
|
}
|
|
5035
|
+
rememberModel();
|
|
5036
|
+
if (fast !== null)
|
|
5037
|
+
recordAgentFast(meta, await switchCodexFast(name, meta, fast, fastVerified));
|
|
5038
|
+
else
|
|
5039
|
+
await dropFastIfUnsupported();
|
|
5040
|
+
return configured();
|
|
5041
|
+
}
|
|
5042
|
+
/** Codexの `/model` の選ぶ画面で、modelとreasoning effortを替える。 */
|
|
5043
|
+
async function configureCodexModel(name, model, effort) {
|
|
5044
|
+
try {
|
|
5045
|
+
await chooseCodexModel(name, model, effort);
|
|
5046
|
+
}
|
|
5047
|
+
catch (error) {
|
|
5048
|
+
// 選べずに断る時は、開いた選ぶ画面を閉じてから断る(開いたままだと、席は次の文を受け付けない)。
|
|
5049
|
+
for (let i = 0; i < 3; i++) {
|
|
5050
|
+
const tail = captureScreen(name, AGENT_TUI_READY_LINES).split("\n").map((line) => line.trim()).filter(Boolean).slice(-2).join("\n");
|
|
5051
|
+
if (!/esc back/.test(tail))
|
|
5052
|
+
break;
|
|
5053
|
+
sendMenuChoice(name, "Escape");
|
|
5054
|
+
await sleep(400);
|
|
5055
|
+
}
|
|
5056
|
+
throw error;
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
async function chooseCodexModel(name, model, effort) {
|
|
4387
5060
|
await sendAgentPromptText(name, "/model");
|
|
4388
5061
|
const modelScreen = await waitForScreenText(name, "Select Model and Effort");
|
|
4389
5062
|
if (model) {
|
|
@@ -4414,14 +5087,6 @@ export async function configureAgent(name, opts) {
|
|
|
4414
5087
|
sendMenuChoice(name, "Enter");
|
|
4415
5088
|
}
|
|
4416
5089
|
await waitForScreenText(name, "Model changed to");
|
|
4417
|
-
return {
|
|
4418
|
-
schema: "aiterm.agent-configure-result.v1",
|
|
4419
|
-
session_id: name,
|
|
4420
|
-
provider: "codex",
|
|
4421
|
-
harness: agentHarness("codex"),
|
|
4422
|
-
model,
|
|
4423
|
-
reasoning_effort: effort,
|
|
4424
|
-
};
|
|
4425
5090
|
}
|
|
4426
5091
|
export function __testCodexConfigureChoices(screen, model, effort) {
|
|
4427
5092
|
return {
|
|
@@ -4570,7 +5235,8 @@ async function dispatchAgentTurnInLock(name, text, o) {
|
|
|
4570
5235
|
const startOffset = agentCompletionCursor(meta);
|
|
4571
5236
|
// promptなしで起動したCursorは、最初のdispatch時点ではtranscriptとの相関markerをまだ持たない。
|
|
4572
5237
|
// その1回だけlaunch contextを加え、以後はbind済みconversationへ通常textだけを送る。
|
|
4573
|
-
|
|
5238
|
+
// 画面の確かめを飛ばす送信(force)と、加工なしの送信(raw)には、既定の前置きを打たない(打った文は入力欄がそのまま読む)。
|
|
5239
|
+
const prefaced = splitAgentPreface(meta.kind, o.preface, text, { defaultForWrapped: !o.force && !o.raw });
|
|
4574
5240
|
const dispatchText = meta.kind === "cursor" && !meta.vendor_session_id
|
|
4575
5241
|
? `${subagentInstruction(meta)}\n\n${prefaced.body}`
|
|
4576
5242
|
: prefaced.body;
|
|
@@ -4691,6 +5357,38 @@ async function codexTurnRunningBehindIdleScreen(name, meta, screen) {
|
|
|
4691
5357
|
return false; /* 読めない時は、動いていると数えない */
|
|
4692
5358
|
}
|
|
4693
5359
|
}
|
|
5360
|
+
const CLAUDE_SELECT_MENU_SETTLE_MS = 250;
|
|
5361
|
+
const CLAUDE_SELECT_MENU_CLOSE_TIMEOUT_MS = 3000;
|
|
5362
|
+
/**
|
|
5363
|
+
* Claudeの席へ打つ前に、入力欄の代わりに選ぶ画面が出ていないかを見る。
|
|
5364
|
+
* 選ぶ画面は貼り付けを捨ててEnterだけを読むので、そのまま送ると文は会話へ入らず、選択中の行が選ばれる
|
|
5365
|
+
* (実被弾 2026-10-13: 利用枠に当たった直後の3席で、指揮官氏の文が消えた)。打鍵の前置きは、数字や「j」が選択の操作になる。
|
|
5366
|
+
* 利用枠の選ぶ画面はEscで閉じてから送る(Escは何も選ばない。枠が解けていれば文は普通に入り、解けていなければ番がAPIエラーで返る)。
|
|
5367
|
+
* 見覚えの無い選ぶ画面は、何も打たずに断る。戻り値は行った回復。
|
|
5368
|
+
*/
|
|
5369
|
+
async function clearClaudeSelectMenuBeforeInput(name) {
|
|
5370
|
+
if (!claudeLiveSelectMenu(captureScreen(name, 0)))
|
|
5371
|
+
return [];
|
|
5372
|
+
// 描き直しの途中の1枚で打たない。少し置いた2枚目にも同じ画面がある時だけ、開いていると数える。
|
|
5373
|
+
await sleep(CLAUDE_SELECT_MENU_SETTLE_MS);
|
|
5374
|
+
const menu = claudeLiveSelectMenu(captureScreen(name, 0));
|
|
5375
|
+
if (!menu)
|
|
5376
|
+
return [];
|
|
5377
|
+
if (menu.kind !== "rate_limit_options") {
|
|
5378
|
+
throw new AitermError(`CLAUDE_SELECT_MENU_OPEN vendor=claude session=${name}\n` +
|
|
5379
|
+
`Claude Codeの選ぶ画面が開いています(見出し: ${menu.heading ?? "不明"})。打った文字は選択の操作として読まれるため、文字列は送信していません。` +
|
|
5380
|
+
"pty_read(screen:true) で画面を確かめてください。", 2);
|
|
5381
|
+
}
|
|
5382
|
+
sendMenuChoice(name, "Escape");
|
|
5383
|
+
for (const deadline = performance.now() + CLAUDE_SELECT_MENU_CLOSE_TIMEOUT_MS; performance.now() < deadline;) {
|
|
5384
|
+
await sleep(100);
|
|
5385
|
+
if (!claudeLiveSelectMenu(captureScreen(name, 0)))
|
|
5386
|
+
return ["claude_rate_limit_menu_closed"];
|
|
5387
|
+
}
|
|
5388
|
+
throw new AitermError(`CLAUDE_RATE_LIMIT_MENU_NOT_CLOSED vendor=claude session=${name}\n` +
|
|
5389
|
+
"利用枠の上限の選ぶ画面(/rate-limit-options)をEscで閉じられませんでした。文字列は送信していません。" +
|
|
5390
|
+
"pty_read(screen:true) で画面を確かめてください。", 2);
|
|
5391
|
+
}
|
|
4694
5392
|
/**
|
|
4695
5393
|
* agent sessionへの唯一の送信口。呼び出し側は子の状態を知らないまま呼び、Aitermがこの時点の画面で振り分ける。
|
|
4696
5394
|
* 実行中なら現在のturnへ差し込み(steer)、そうでなければ新しいturnとしてdispatchする。
|
|
@@ -4709,6 +5407,8 @@ async function sendAgentMessageInLock(name, text, o) {
|
|
|
4709
5407
|
const meta = loadAgentMetadata(name);
|
|
4710
5408
|
// 前面回復は busy 判定より先(bash 前面のままだと画面の実行中マーカーを読んでも打鍵が届かない)。
|
|
4711
5409
|
const paneInputRecovery = await ensureAgentOwnsPaneInput(name, meta.kind);
|
|
5410
|
+
if (meta.kind === "claude")
|
|
5411
|
+
paneInputRecovery.push(...await clearClaudeSelectMenuBeforeInput(name));
|
|
4712
5412
|
const screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
4713
5413
|
let running = isAgentTuiBusy(meta.kind, screen);
|
|
4714
5414
|
// Claudeのtool処理中は画面のbusy表示が消えることがある。Stopまで保持するturnの印を正とし、
|
|
@@ -4754,7 +5454,7 @@ async function steerRunningTurn(name, meta, text, o) {
|
|
|
4754
5454
|
harness: agentHarness(meta.kind),
|
|
4755
5455
|
pane_input_recovery: o.pane_input_recovery,
|
|
4756
5456
|
};
|
|
4757
|
-
const prefaced = splitAgentPreface(meta.kind, o.preface, text);
|
|
5457
|
+
const prefaced = splitAgentPreface(meta.kind, o.preface, text, { defaultForWrapped: !o.raw });
|
|
4758
5458
|
text = prefaced.body;
|
|
4759
5459
|
prepareSendText(text, { raw: o.raw });
|
|
4760
5460
|
// Claudeのoperation相関は変えない。差し込みは実行中turnの一部であり、新しいturnを予約しない。
|
|
@@ -4915,15 +5615,22 @@ export function harnessLauncherDiagnostic(kind) {
|
|
|
4915
5615
|
return "unverified";
|
|
4916
5616
|
}
|
|
4917
5617
|
}
|
|
4918
|
-
function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
|
|
5618
|
+
function buildAgentCmd(kind, bin, model, effort, prompt, meta = null, cursorFast = false) {
|
|
4919
5619
|
if (kind === "claude")
|
|
4920
5620
|
return buildClaudeAgentCmd(bin, model, effort, prompt, meta);
|
|
4921
5621
|
if (kind === "codex")
|
|
4922
5622
|
return buildCodexAgentCmd(bin, model, effort, prompt, meta);
|
|
4923
5623
|
if (kind === "cursor")
|
|
4924
|
-
return buildCursorAgentCmd(bin, model, effort, prompt, meta);
|
|
5624
|
+
return buildCursorAgentCmd(bin, model, effort, prompt, meta, cursorFast);
|
|
4925
5625
|
return buildGrokAgentCmd(kind, bin, model, effort, prompt, meta);
|
|
4926
5626
|
}
|
|
5627
|
+
/**
|
|
5628
|
+
* 席の会話ID(`--session-id`で起こすClaude CodeとGrokだけ、起動の時点で決まっている)。席のprocessが起こした子にも起動の印
|
|
5629
|
+
* (AITERM_AGENT_LAUNCH_ID)は継がれるので、hookなどが「席そのものの会話か」を見分ける時は、この値を自分の会話IDと比べる(ADR 0118)。
|
|
5630
|
+
*/
|
|
5631
|
+
function agentVendorSessionEnv(meta) {
|
|
5632
|
+
return meta.vendor_session_id ? [["AITERM_AGENT_VENDOR_SESSION_ID", meta.vendor_session_id]] : [];
|
|
5633
|
+
}
|
|
4927
5634
|
/** Cursor用のagent環境変数(名前と値)。Windowsでは引用規則の違うPowerShell paneへ渡す。 */
|
|
4928
5635
|
function cursorAgentEnvEntries(meta, sid, envVars = []) {
|
|
4929
5636
|
const inherited = envVars.flatMap((name) => {
|
|
@@ -4936,6 +5643,7 @@ function cursorAgentEnvEntries(meta, sid, envVars = []) {
|
|
|
4936
5643
|
["AITERM_SESSION_ID", sid],
|
|
4937
5644
|
["AITERM_AGENT_SESSION_ID", sid],
|
|
4938
5645
|
["AITERM_AGENT_LAUNCH_ID", meta.launch_id],
|
|
5646
|
+
...agentVendorSessionEnv(meta),
|
|
4939
5647
|
["AITERM_AGENT_ROLE", meta.agent_role ?? "subagent"],
|
|
4940
5648
|
["AITERM_AGENT_PARENT_SESSION_ID", meta.parent_session_id ?? "host-root"],
|
|
4941
5649
|
["AITERM_AGENT_DEPTH", String(meta.delegation_depth ?? 1)],
|
|
@@ -4958,6 +5666,7 @@ function agentEnvPrefix(meta, sid, envVars = []) {
|
|
|
4958
5666
|
`AITERM_SESSION_ID=${shq(sid)}`,
|
|
4959
5667
|
`AITERM_AGENT_SESSION_ID=${shq(sid)}`,
|
|
4960
5668
|
`AITERM_AGENT_LAUNCH_ID=${shq(meta.launch_id)}`,
|
|
5669
|
+
...agentVendorSessionEnv(meta).map(([name, value]) => `${name}=${shq(value)}`),
|
|
4961
5670
|
`AITERM_AGENT_ROLE=${shq(meta.agent_role ?? "subagent")}`,
|
|
4962
5671
|
`AITERM_AGENT_PARENT_SESSION_ID=${shq(meta.parent_session_id ?? "host-root")}`,
|
|
4963
5672
|
`AITERM_AGENT_DEPTH=${shq(String(meta.delegation_depth ?? 1))}`,
|
|
@@ -5129,8 +5838,9 @@ export function openAgent(kind, opts = {}) {
|
|
|
5129
5838
|
if (model)
|
|
5130
5839
|
assertGrokModelAvailable(bin, cwd ?? process.cwd(), model);
|
|
5131
5840
|
}
|
|
5841
|
+
const cursorFast = kind === "cursor" && opts.cursor_fast === true;
|
|
5132
5842
|
if (kind === "cursor" && model) {
|
|
5133
|
-
assertCursorModelAvailable(bin, cwd ?? process.cwd(), model, effort);
|
|
5843
|
+
assertCursorModelAvailable(bin, cwd ?? process.cwd(), model, effort, cursorFast);
|
|
5134
5844
|
}
|
|
5135
5845
|
let sid;
|
|
5136
5846
|
let hint;
|
|
@@ -5160,16 +5870,19 @@ export function openAgent(kind, opts = {}) {
|
|
|
5160
5870
|
: null;
|
|
5161
5871
|
if (meta) {
|
|
5162
5872
|
meta.agent_executable = bin;
|
|
5873
|
+
// 席は通常の速度で始まる(Claude Code・Codexは起動の引数で明示する)。fast版のmodel IDで起こした時だけtrue。
|
|
5874
|
+
meta.fast = opts.fast_state === true;
|
|
5875
|
+
meta.model = opts.base_model !== undefined ? opts.base_model : model;
|
|
5163
5876
|
writeAgentMetadata(meta);
|
|
5164
5877
|
agentMetadataNegativeCache.delete(sid);
|
|
5165
5878
|
}
|
|
5166
5879
|
launchNote = buildAgentLaunchNote(kind, model, effort, meta);
|
|
5167
5880
|
let full;
|
|
5168
5881
|
if (paneShell === "pwsh" && meta) {
|
|
5169
|
-
full = cursorPwshLaunchLine(cwd, cursorAgentEnvEntries(meta, sid, envVars), cursorAgentArgv(bin, model, effort, opts.prompt ?? null, meta));
|
|
5882
|
+
full = cursorPwshLaunchLine(cwd, cursorAgentEnvEntries(meta, sid, envVars), cursorAgentArgv(bin, model, effort, opts.prompt ?? null, meta, cursorFast));
|
|
5170
5883
|
}
|
|
5171
5884
|
else {
|
|
5172
|
-
const cmd = buildAgentCmd(kind, binForCmd, model, effort, opts.prompt ?? null, meta);
|
|
5885
|
+
const cmd = buildAgentCmd(kind, binForCmd, model, effort, opts.prompt ?? null, meta, cursorFast);
|
|
5173
5886
|
const envPrefix = agentEnvPrefix(meta, sid, envVars);
|
|
5174
5887
|
full = cwdForCmd ? `cd ${shq(cwdForCmd)} && ${envPrefix}${cmd}` : `${envPrefix}${cmd}`;
|
|
5175
5888
|
}
|
|
@@ -5248,25 +5961,82 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
5248
5961
|
if (opts.launch_operation_id != null && prompt !== null) {
|
|
5249
5962
|
throw new AitermError("launch_operation_idはpromptなしのClaude相関launchだけで指定できます", 2);
|
|
5250
5963
|
}
|
|
5964
|
+
// ファスト(ADR 0114)。設定の誤りは席を作る前に断る。fastへ出来ない訳がアカウントの都合なら、通常で起こして返りへ訳を入れる。
|
|
5965
|
+
const requestedModel = opts.model == null ? null : opts.model.trim() || null;
|
|
5966
|
+
const fastPlan = await planLaunchFast(kind, {
|
|
5967
|
+
model: requestedModel, effort: opts.reasoning_effort ?? null, fast: opts.fast ?? null, cwd: opts.cwd ?? null,
|
|
5968
|
+
});
|
|
5969
|
+
const fastResult = { fast: fastPlan.state, fast_error: fastPlan.fast_error };
|
|
5970
|
+
const openOptions = {
|
|
5971
|
+
session_name: opts.session_name ?? null,
|
|
5972
|
+
// modelは呼び手の渡した形のまま渡す(空文字の断りはopenAgentがする)。Grokのfastだけ、fast版のIDへ替える。
|
|
5973
|
+
model: fastPlan.model !== requestedModel ? fastPlan.model : opts.model ?? null,
|
|
5974
|
+
reasoning_effort: opts.reasoning_effort ?? null,
|
|
5975
|
+
cwd: opts.cwd ?? null,
|
|
5976
|
+
prompt: null,
|
|
5977
|
+
agent_done: true,
|
|
5978
|
+
launch_operation_id: opts.launch_operation_id ?? null,
|
|
5979
|
+
write_scope: opts.write_scope,
|
|
5980
|
+
env_vars: opts.env_vars,
|
|
5981
|
+
cursor_fast: fastPlan.cursor_fast,
|
|
5982
|
+
fast_state: fastPlan.state,
|
|
5983
|
+
base_model: requestedModel,
|
|
5984
|
+
};
|
|
5985
|
+
// GrokとCursorは、起動の --model の値を利用者の既定へ書く(Cursorは版による)。起動の前後で、直前の値へ戻す。
|
|
5986
|
+
const launch = () => {
|
|
5987
|
+
if (kind === "grok") {
|
|
5988
|
+
const configPath = grokUserConfigPath(realGrokHome());
|
|
5989
|
+
// Grokは起動完了の直後に書く(入力待ちと読める頃には書き終えている)。
|
|
5990
|
+
return launchKeepingUserDefaults(configPath, {
|
|
5991
|
+
restore: restoreGrokDefaults(configPath), written: grokDefaultsWritten, mustWrite: () => false, ready: grokTuiReady,
|
|
5992
|
+
}, () => openAgent(kind, openOptions));
|
|
5993
|
+
}
|
|
5994
|
+
if (kind === "cursor" && openOptions.model) {
|
|
5995
|
+
const configPath = cursorUserConfigPath(realCursorHome());
|
|
5996
|
+
// Cursorは、既定のmodelを設定に持つ端末(selectedModel の項目がある)で、起動の --model を既定へ書く。書くのは、入力待ちになった後の事がある。
|
|
5997
|
+
return launchKeepingUserDefaults(configPath, {
|
|
5998
|
+
restore: restoreCursorDefaults(configPath), written: cursorDefaultsWritten,
|
|
5999
|
+
mustWrite: (before) => { const config = parseJsonObject(before); return config !== null && "selectedModel" in config; },
|
|
6000
|
+
ready: cursorTuiReady,
|
|
6001
|
+
}, () => openAgent(kind, openOptions));
|
|
6002
|
+
}
|
|
6003
|
+
return Promise.resolve().then(() => openAgent(kind, openOptions));
|
|
6004
|
+
};
|
|
6005
|
+
// 入力待ちになった席を、その席の中でfastへ切り替える(Claude Code・Codex)。出来なかった訳は返りへ入れ、起動は続ける。
|
|
6006
|
+
const switchAfterReady = async (name, meta) => {
|
|
6007
|
+
if (!fastPlan.switch_after_ready)
|
|
6008
|
+
return;
|
|
6009
|
+
fastResult.fast_error = await applyLaunchFast(name, meta);
|
|
6010
|
+
fastResult.fast = meta.fast === true;
|
|
6011
|
+
};
|
|
5251
6012
|
// v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
|
|
5252
6013
|
// pty_open で素の PTY を開き、harness CLI を自分で send する。
|
|
5253
6014
|
// prompt無しはTUIを起動するだけ。prompt有りはharnessを問わず、TUI ready確認後の
|
|
5254
6015
|
// sendInitialAgentPromptへ一本化する。Grokの--verbatim argvはCLIが本文をqueueへ置いただけで
|
|
5255
6016
|
// turnを開始しない版があり、event_cursor=0を「実行中」と返すと利用側へ嘘をつく。
|
|
5256
6017
|
if (!prompt) {
|
|
5257
|
-
const [sid, hint] =
|
|
5258
|
-
session_name: opts.session_name ?? null,
|
|
5259
|
-
model: opts.model ?? null,
|
|
5260
|
-
reasoning_effort: opts.reasoning_effort ?? null,
|
|
5261
|
-
cwd: opts.cwd ?? null,
|
|
5262
|
-
prompt: null,
|
|
5263
|
-
agent_done: true,
|
|
5264
|
-
launch_operation_id: opts.launch_operation_id ?? null,
|
|
5265
|
-
write_scope: opts.write_scope,
|
|
5266
|
-
env_vars: opts.env_vars,
|
|
5267
|
-
});
|
|
6018
|
+
const [sid, hint] = await launch();
|
|
5268
6019
|
const delivery = { status: "not_requested", reason: "not_requested", turn_started: false };
|
|
5269
6020
|
let startup = { status: "not_checked", reason: "startup_not_requested" };
|
|
6021
|
+
if (opts.trust_project !== true && fastPlan.switch_after_ready) {
|
|
6022
|
+
// 起動の準備待ちを頼まれていなくても、切り替えは入力待ちの席にしか打てない。待って、なれなければ通常のまま返す。
|
|
6023
|
+
const meta = loadAgentMetadata(sid);
|
|
6024
|
+
let ready = { status: "blocked", reason: "startup_failed" };
|
|
6025
|
+
try {
|
|
6026
|
+
ready = await prepareAgentInput(sid, meta, { ready_timeout: opts.ready_timeout ?? undefined });
|
|
6027
|
+
}
|
|
6028
|
+
catch { /* 訳は下の fast_error で返す */ }
|
|
6029
|
+
if (ready.status === "ready") {
|
|
6030
|
+
startup = ready;
|
|
6031
|
+
await switchAfterReady(sid, meta);
|
|
6032
|
+
}
|
|
6033
|
+
else {
|
|
6034
|
+
fastResult.fast_error = {
|
|
6035
|
+
code: "FAST_MODE_UNCONFIRMED",
|
|
6036
|
+
message: `FAST_MODE_UNCONFIRMED: 席が入力待ちにならず(reason=${ready.reason})、fastへ切り替えていません。席は通常の速度です。`,
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
5270
6040
|
if (opts.trust_project === true) {
|
|
5271
6041
|
try {
|
|
5272
6042
|
startup = await prepareAgentInput(sid, loadAgentMetadata(sid), {
|
|
@@ -5278,28 +6048,20 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
5278
6048
|
}
|
|
5279
6049
|
if (startup.status !== "ready")
|
|
5280
6050
|
throw new AgentLaunchPromptError(`session_id: ${sid}\n起動準備を完了できませんでした。reason=${startup.reason}${startupBlockedHint(startup.reason)}`, 2, sid, delivery, null, startup);
|
|
6051
|
+
await switchAfterReady(sid, loadAgentMetadata(sid));
|
|
5281
6052
|
}
|
|
5282
|
-
return [sid, hint, null, null, delivery, startup];
|
|
6053
|
+
return [sid, hint, null, null, delivery, startup, fastResult];
|
|
5283
6054
|
}
|
|
5284
|
-
const [sid, hint] =
|
|
5285
|
-
session_name: opts.session_name ?? null,
|
|
5286
|
-
model: opts.model ?? null,
|
|
5287
|
-
reasoning_effort: opts.reasoning_effort ?? null,
|
|
5288
|
-
cwd: opts.cwd ?? null,
|
|
5289
|
-
prompt: null,
|
|
5290
|
-
agent_done: true,
|
|
5291
|
-
launch_operation_id: opts.launch_operation_id ?? null,
|
|
5292
|
-
write_scope: opts.write_scope,
|
|
5293
|
-
env_vars: opts.env_vars,
|
|
5294
|
-
});
|
|
6055
|
+
const [sid, hint] = await launch();
|
|
5295
6056
|
try {
|
|
5296
6057
|
const initial = await sendInitialAgentPrompt(sid, prompt, {
|
|
5297
6058
|
ready_timeout: opts.ready_timeout ?? undefined,
|
|
5298
6059
|
trust_project: opts.trust_project,
|
|
5299
6060
|
before_send: opts.before_send,
|
|
6061
|
+
after_ready: switchAfterReady,
|
|
5300
6062
|
});
|
|
5301
6063
|
return [sid, `${hint}\n${initial.text}`, initial.event_cursor, initial.submit_residue, initial.initial_prompt,
|
|
5302
|
-
{ status: "ready", reason: "composer_ready" }];
|
|
6064
|
+
{ status: "ready", reason: "composer_ready" }, fastResult];
|
|
5303
6065
|
}
|
|
5304
6066
|
catch (e) {
|
|
5305
6067
|
const code = e instanceof AitermError ? e.code : 1;
|