aiterm-mcp 0.60.1 → 0.61.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 +14 -1
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/claude-operation-marker.js +11 -0
- package/dist/claude-stop-hook.js +5 -9
- package/dist/core.js +537 -44
- package/dist/execution-checkpoint.js +83 -0
- package/dist/execution-close.js +82 -0
- package/dist/execution-context.js +19 -0
- package/dist/execution-contract.js +263 -0
- package/dist/execution-dispatch.js +285 -0
- package/dist/execution-effect.js +411 -0
- package/dist/execution-immediate-stop.js +68 -0
- package/dist/execution-interruption.js +150 -0
- package/dist/execution-inventory.js +100 -0
- package/dist/execution-lock.js +136 -0
- package/dist/execution-native-server.js +140 -0
- package/dist/execution-native-setup.js +64 -0
- package/dist/execution-parent-alias.js +75 -0
- package/dist/execution-parent-claude-records.js +136 -0
- package/dist/execution-parent-endpoint.js +92 -0
- package/dist/execution-parent-manager.js +89 -0
- package/dist/execution-parent-native.js +179 -0
- package/dist/execution-parent-process.js +29 -0
- package/dist/execution-parent-producer.js +129 -0
- package/dist/execution-parent-queue.js +84 -0
- package/dist/execution-parent-source.js +200 -0
- package/dist/execution-parent-worker.js +89 -0
- package/dist/execution-parent.js +345 -0
- package/dist/execution-participant-store.js +107 -0
- package/dist/execution-participant.js +78 -0
- package/dist/execution-runtime.js +182 -0
- package/dist/execution-service.js +1334 -0
- package/dist/execution-steer.js +37 -0
- package/dist/execution-store.js +206 -0
- package/dist/execution-tools.js +151 -0
- package/dist/execution-wire.js +129 -0
- package/dist/harnesses/claude.js +2 -0
- package/dist/harnesses/codex.js +111 -0
- package/dist/harnesses/cursor.js +2 -0
- package/dist/harnesses/grok.js +2 -0
- package/dist/index.js +1196 -1122
- package/dist/parent-delivery.js +408 -41
- package/dist/process-runtime.js +28 -0
- package/dist/tmux-runtime.js +30 -4
- package/docs/DESIGN.md +60 -0
- package/docs/RELEASE.md +19 -1
- package/package.json +4 -3
package/dist/core.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseClaudeOperationMarker } from "./claude-operation-marker.js";
|
|
1
2
|
/**
|
|
2
3
|
* core — CLI/MCP が共有する純粋ロジック層(Node/TS 版)。
|
|
3
4
|
*
|
|
@@ -13,10 +14,15 @@ import * as path from "node:path";
|
|
|
13
14
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
15
|
import { fileURLToPath } from "node:url";
|
|
15
16
|
import * as rtk from "./rtk.js";
|
|
17
|
+
import { closeExpectedExecution, parseExecutionNativeSessions, executionNativeBindingSchema } from "./execution-close.js";
|
|
18
|
+
import { executionPointSchema } from "./execution-contract.js";
|
|
19
|
+
import { withExecutionInterruptEffectScope, bindExecutionNativeTarget, beforeExecutionEffect, bindExecutionCreatedLaunch, executionCreatedNativeBinding, hasExecutionCreationScope, assertExecutionContext, withExecutionPreparation, captureExecutionSendBoundary, readExecutionSendBoundary } from "./execution-effect.js";
|
|
20
|
+
import { ExecutionError } from "./execution-wire.js";
|
|
21
|
+
import { readExecutionParentProcess } from "./execution-parent-process.js";
|
|
16
22
|
import { isCursorMcpClient } from "aiterm-steer-delivery";
|
|
17
23
|
import { paneTokenHint } from "./harnesses/pane-tokens.js";
|
|
18
24
|
import { unfinishedDeliveriesOwnedBy } from "./parent-delivery-owners.js";
|
|
19
|
-
import { readRuntimeProcesses, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost, processElapsedSeconds } from "./process-runtime.js";
|
|
25
|
+
import { readRuntimeProcesses, readProcessIdentities, processSubtree, parentProcess, processIdentity, backgroundProcesses, lazyRelayProcess, aitermProcessProbe, windowsConsoleHost, processElapsedSeconds } from "./process-runtime.js";
|
|
20
26
|
import { liveNodeExecutable } from "./live-node.js";
|
|
21
27
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
22
28
|
import { isWin, SOCKDIR, tmuxCommand, sendPsmuxPayload, loadPtyBufferChunk, pasteBufferBaseArgs, tmuxNewSession, openPsmuxServerToDesktop, registeredSessionEnvironment, attachCommand, normalizePaneCommand, atomicShellMultiline, appendMarkSentinel, markShellCommand, settlePaneLog, paneCwdArgument, sessionEnvironmentLaunch, } from "./tmux-runtime.js";
|
|
@@ -25,10 +31,10 @@ import { catalogUnavailable } from "./model-catalog.js";
|
|
|
25
31
|
import { FastModeError, fastModeFailure } from "./fast-mode.js";
|
|
26
32
|
import { readFileOrNull, restoreJsonSetting, withUserConfigLock } from "./user-config.js";
|
|
27
33
|
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, cursorPwshArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
34
|
+
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, GROK_INTERRUPT_KEY, grokRateLimitDialog, grokPromptAwaitingAccept, grokUnacceptedPromptRestored, grokStartupAction, grokLaunchBlockingDialog, assertGrokSandboxNotRejected, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./harnesses/grok.js";
|
|
35
|
+
import { codexAuthPlan, codexAuthStatus, codexAuthPane, realCodexHome, readCodexConfigPins, bindCodexTranscriptSession, latestCodexCompletion, codexTurnSettled, codexRunningTurn, codexInterruptionRecord, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, codexPaneObservation, CODEX_INTERRUPT_KEY, codexTuiBusy, codexHelperProcess, codexRateLimitModelSwitchDialog, codexApprovalDialog, codexStartupAction, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, codexModelChoices, codexFastCommandOffered, codexFooterShowsFast, codexServiceTierNotices, restoreCodexDefaults, restoreCodexDefaultInFile, } from "./harnesses/codex.js";
|
|
36
|
+
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, CLAUDE_INTERRUPT_KEY, claudeUsageLimit, claudeLiveSelectMenu, claudeModelPicker, claudeModelPickerRow, claudeModelSwitchConfirm, claudeModelSwitchResult, claudeStartupAction, claudeLoginMethodMenu, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, claudeSessionTranscriptPath, claudeApiErrorFromLine, claudeApiErrorAfter, } from "./harnesses/claude.js";
|
|
37
|
+
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, cursorPwshArgv, cursorPwshLaunchLine, cursorPromptWithLineage, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, cursorPaneObservation, CURSOR_INTERRUPT_KEY, cursorPromptHooksRunning, cursorUsageLimit, CURSOR_SUBMIT_SEQUENCE, CURSOR_COMPOSER_CONTENT_MARKER_RE, validateCursorModelEffort, } from "./harnesses/cursor.js";
|
|
32
38
|
import { interimFromNow, readInterimWords, recordInterimBoundary } from "./interim-words.js";
|
|
33
39
|
import { codexSeatTurn, observeSteerDone, readSteerEntry, recordSteer } from "./steer-follow.js";
|
|
34
40
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
@@ -191,7 +197,7 @@ export function termiosIsCooked(sttyOutput) {
|
|
|
191
197
|
return tokens.includes("icanon") || tokens.includes("echo");
|
|
192
198
|
}
|
|
193
199
|
function paneTtyOf(name) {
|
|
194
|
-
const r = tmux("display-message", "-p", "-t", name
|
|
200
|
+
const r = tmux("display-message", "-p", "-t", `=${name}:`, "#{pane_tty}");
|
|
195
201
|
if (r.code !== 0)
|
|
196
202
|
return null;
|
|
197
203
|
const tty = r.stdout.trim();
|
|
@@ -203,6 +209,7 @@ function paneTtyOf(name) {
|
|
|
203
209
|
* agent が tty 上に実在しない時は触らない(ready gate が入力受付不能として止める)。
|
|
204
210
|
*/
|
|
205
211
|
export async function ensureAgentOwnsPaneInput(name, kind) {
|
|
212
|
+
assertExecutionContext(name);
|
|
206
213
|
if (isWin)
|
|
207
214
|
return [];
|
|
208
215
|
const tty = paneTtyOf(name);
|
|
@@ -240,6 +247,13 @@ export async function ensureAgentOwnsPaneInput(name, kind) {
|
|
|
240
247
|
const ttyFlag = process.platform === "linux" ? "-F" : "-f";
|
|
241
248
|
const stty = spawnSync("/bin/stty", [ttyFlag, tty, "-a"], { encoding: "utf8", timeout: 5000, env: psEnv });
|
|
242
249
|
if (stty.status === 0 && termiosIsCooked(stty.stdout ?? "")) {
|
|
250
|
+
// stty is a native mutation outside tmux's command gate. Recheck the
|
|
251
|
+
// captured device and the shared request generation/fence immediately
|
|
252
|
+
// before dispatch; a replaced pane must not inherit the old tty repair.
|
|
253
|
+
beforeExecutionEffect(name, () => {
|
|
254
|
+
if (paneTtyOf(name) !== tty)
|
|
255
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
256
|
+
});
|
|
243
257
|
const set = spawnSync("/bin/stty", [ttyFlag, tty, "raw", "-echo"], { encoding: "utf8", timeout: 5000, env: psEnv });
|
|
244
258
|
if (set.status !== 0) {
|
|
245
259
|
throw new AitermError(`AGENT_TTY_COOKED session=${name}\n` +
|
|
@@ -667,6 +681,7 @@ function rtkRewrite(text) {
|
|
|
667
681
|
// WSL launcher (bash.exe) に解決されてしまうため、Git for Windows の bash.exe を
|
|
668
682
|
// 明示解決する(WSL 非依存の裁定に従う)。AITERM_BASH で上書き可。
|
|
669
683
|
export function openSession(name, shell = "bash", envVars = []) {
|
|
684
|
+
assertExecutionContext(name);
|
|
670
685
|
shell = resolveWinPaneShell(shell);
|
|
671
686
|
for (const key of envVars) {
|
|
672
687
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
@@ -823,6 +838,7 @@ function prepareSendText(text, o) {
|
|
|
823
838
|
return text;
|
|
824
839
|
}
|
|
825
840
|
export function send(name, text, o = {}) {
|
|
841
|
+
assertExecutionContext(name);
|
|
826
842
|
assertSessionName(name);
|
|
827
843
|
const enter = o.enter ?? true;
|
|
828
844
|
if (!sessionExists(name))
|
|
@@ -948,10 +964,20 @@ export function send(name, text, o = {}) {
|
|
|
948
964
|
}
|
|
949
965
|
}
|
|
950
966
|
export function sendKey(name, key, o = {}) {
|
|
967
|
+
const result = sendKeyResult(name, key, o);
|
|
968
|
+
return `sent key ${result.key} to ${result.session_id}`;
|
|
969
|
+
}
|
|
970
|
+
/** Native transport acknowledgement only. This does not observe a turn stop,
|
|
971
|
+
* a launch/conversation identity, or permission to retry a failed delivery.
|
|
972
|
+
*/
|
|
973
|
+
export function sendKeyResult(name, key, o = {}) {
|
|
974
|
+
assertExecutionContext(name);
|
|
951
975
|
assertSessionName(name);
|
|
952
976
|
if (!sessionExists(name))
|
|
953
977
|
throw new AitermError(`session '${name}' が無い`, 2);
|
|
954
978
|
const k = KEYMAP[key.toLowerCase()] ?? key;
|
|
979
|
+
const receipt = () => ({ schema: "aiterm.pty-key-result.v1", session_id: name, key: k,
|
|
980
|
+
outcome: "accepted", interruption: "unconfirmed", session_preserved: "unconfirmed" });
|
|
955
981
|
if (!o.preserveAgentOperation && managedClaudeOperation(name) !== undefined && k !== "C-c") {
|
|
956
982
|
throw new AitermError("aiterm相関付きClaude agent sessionではturn相関を壊さないC-cだけをpty_keyで送れます。" +
|
|
957
983
|
"他の対話操作はpty_send(自動dispatch)、終了はpty_closeを使ってください。", 2);
|
|
@@ -964,10 +990,303 @@ export function sendKey(name, key, o = {}) {
|
|
|
964
990
|
raw: true,
|
|
965
991
|
preserveAgentOperation: o.preserveAgentOperation,
|
|
966
992
|
});
|
|
967
|
-
return
|
|
993
|
+
return receipt();
|
|
994
|
+
}
|
|
995
|
+
const result = tmux("send-keys", "-t", name, k);
|
|
996
|
+
if (result.code !== 0)
|
|
997
|
+
throw new AitermError("制御キー送信を確認できません。自動再送やcloseへの切替はしていません。", 2);
|
|
998
|
+
return receipt();
|
|
999
|
+
}
|
|
1000
|
+
// Interrupt needs the recorded harness/name/launch, not auth readiness or
|
|
1001
|
+
// transcript/operation proof. An expired login must not block the cancel key.
|
|
1002
|
+
function readInterruptionTarget(name) {
|
|
1003
|
+
try {
|
|
1004
|
+
const files = fs.readdirSync(agentsDir()).filter(file => file.startsWith(name + ".") && file.endsWith(".agent.json"));
|
|
1005
|
+
if (files.length !== 1)
|
|
1006
|
+
return null;
|
|
1007
|
+
const meta = JSON.parse(fs.readFileSync(path.join(agentsDir(), files[0]), "utf8"));
|
|
1008
|
+
return meta.aiterm_session === name && ["codex", "claude", "cursor", "grok"].includes(meta.kind)
|
|
1009
|
+
&& typeof meta.launch_id === "string" && LAUNCH_ID_RE.test(meta.launch_id) ? { kind: meta.kind, launch_id: meta.launch_id } : null;
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
return null;
|
|
968
1013
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1014
|
+
}
|
|
1015
|
+
/** Immediate stop-button operation. No run/actor/claim journal or completion
|
|
1016
|
+
* proof is required to send the harness's normal cancel key. The native session ID prevents a same-name
|
|
1017
|
+
* replacement from receiving the key. This never closes or clears a session. */
|
|
1018
|
+
export function interruptSessionNow(name, expectedLaunchId) {
|
|
1019
|
+
assertSessionName(name);
|
|
1020
|
+
const listing = observeExecutionNativeSessions();
|
|
1021
|
+
if (listing.state !== "known")
|
|
1022
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED");
|
|
1023
|
+
const native = listing.sessions.find(session => session.session_id === name);
|
|
1024
|
+
if (!native)
|
|
1025
|
+
return { session_id: name, outcome: "idle" };
|
|
1026
|
+
const meta = readInterruptionTarget(name);
|
|
1027
|
+
if (!meta || !["codex", "claude", "cursor", "grok"].includes(meta.kind))
|
|
1028
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1029
|
+
const key = meta.kind === "codex" ? CODEX_INTERRUPT_KEY : meta.kind === "claude" ? CLAUDE_INTERRUPT_KEY
|
|
1030
|
+
: meta.kind === "cursor" ? CURSOR_INTERRUPT_KEY : GROK_INTERRUPT_KEY;
|
|
1031
|
+
// A normal Ctrl+C can quit an idle CLI. Use its existing immediate pane
|
|
1032
|
+
// observation, never repeated Ctrl+C or kill as an escalation.
|
|
1033
|
+
if (key === "C-c") {
|
|
1034
|
+
const screen = captureScreen(name, AGENT_TUI_READY_LINES);
|
|
1035
|
+
const state = (meta.kind === "cursor" ? cursorPaneObservation(screen) : grokPaneObservation(screen)).state;
|
|
1036
|
+
if (state === "idle")
|
|
1037
|
+
return { session_id: name, outcome: "idle" };
|
|
1038
|
+
if (state !== "busy")
|
|
1039
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED");
|
|
1040
|
+
}
|
|
1041
|
+
return withExecutionInterruptEffectScope(name, native.native_id, () => {
|
|
1042
|
+
if (expectedLaunchId && readInterruptionTarget(name)?.launch_id !== expectedLaunchId)
|
|
1043
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1044
|
+
}, () => {
|
|
1045
|
+
bindExecutionNativeTarget(name, native.native_id);
|
|
1046
|
+
beforeExecutionEffect(name);
|
|
1047
|
+
const result = tmux("send-keys", "-t", native.native_id, key);
|
|
1048
|
+
if (result.code !== 0)
|
|
1049
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1050
|
+
return { schema: "aiterm.pty-key-result.v1", session_id: name, key, outcome: "accepted",
|
|
1051
|
+
interruption: "unconfirmed", session_preserved: "unconfirmed" };
|
|
1052
|
+
}, key);
|
|
1053
|
+
}
|
|
1054
|
+
export function interruptionLaunchId(name) {
|
|
1055
|
+
assertSessionName(name);
|
|
1056
|
+
return readInterruptionTarget(name)?.launch_id ?? null;
|
|
1057
|
+
}
|
|
1058
|
+
/** Private minimal harness primitive. The caller already owns the current
|
|
1059
|
+
* run's native admission frame. No public pty_key guard bypass is exposed to
|
|
1060
|
+
* Bridge/model args, and agent_stop remains disconnected until preservation
|
|
1061
|
+
* and interruption/fence evidence are implemented. Never close or retry.
|
|
1062
|
+
*/
|
|
1063
|
+
export function interruptExpectedAgentRun(input) {
|
|
1064
|
+
const point = executionPointSchema.parse(input), name = point.launch.session_id;
|
|
1065
|
+
assertExecutionContext(name);
|
|
1066
|
+
if (!point.boundary)
|
|
1067
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1068
|
+
const verifyRun = (meta) => {
|
|
1069
|
+
if (meta.kind === "codex") {
|
|
1070
|
+
if (!point.boundary.turn_id)
|
|
1071
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1072
|
+
const record = codexInterruptionRecord(meta);
|
|
1073
|
+
if (!record)
|
|
1074
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1075
|
+
if (record.boundary.type !== "task_started" || record.boundary.turn_id !== point.boundary.turn_id)
|
|
1076
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1077
|
+
return record;
|
|
1078
|
+
}
|
|
1079
|
+
else if (meta.kind === "claude") {
|
|
1080
|
+
const marker = readClaudeOperationMarker(meta);
|
|
1081
|
+
if (!matchesClaudeInterruptionMarker(marker, point))
|
|
1082
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1083
|
+
// Anonymous identity is the v2 marker's original reservation, not a
|
|
1084
|
+
// native turn. Keep its file identity stable through the physical gate.
|
|
1085
|
+
return marker.operationId === null ? observeManagedClaudeInterruption(point) : marker;
|
|
1086
|
+
}
|
|
1087
|
+
else
|
|
1088
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED"); // no guessed C-c/Esc for Cursor/Grok
|
|
1089
|
+
};
|
|
1090
|
+
const current = () => {
|
|
1091
|
+
const observed = observeExecutionSessionGeneration(name);
|
|
1092
|
+
if (!observed.exists || !observed.launch_id)
|
|
1093
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1094
|
+
if (observed.launch_id !== point.launch.launch_id)
|
|
1095
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1096
|
+
const meta = loadAgentMetadata(name);
|
|
1097
|
+
if (meta.launch_id !== point.launch.launch_id)
|
|
1098
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1099
|
+
const run = verifyRun(meta);
|
|
1100
|
+
const process = observeExecutionParentProcess(name);
|
|
1101
|
+
const actor = process?.harness_process;
|
|
1102
|
+
if (process?.schema !== "aiterm.execution-parent-process.v1" || !isDeepStrictEqual(process.source?.launch, point.launch)
|
|
1103
|
+
|| process.harness !== agentHarness(meta.kind) || !actor || !Number.isSafeInteger(actor.pid) || actor.pid < 1
|
|
1104
|
+
|| !Number.isFinite(Date.parse(actor.started_identity)) || !meta.execution_native)
|
|
1105
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1106
|
+
const after = observeExecutionSessionGeneration(name), last = loadAgentMetadata(name);
|
|
1107
|
+
if (!after.exists || after.launch_id !== point.launch.launch_id || last.kind !== meta.kind
|
|
1108
|
+
|| !isDeepStrictEqual(last.execution_native, meta.execution_native))
|
|
1109
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1110
|
+
if (!isDeepStrictEqual(verifyRun(last), run))
|
|
1111
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1112
|
+
return { actor, native: meta.execution_native, kind: meta.kind, run };
|
|
1113
|
+
};
|
|
1114
|
+
const before = current();
|
|
1115
|
+
bindExecutionNativeTarget(name, before.native.native_id);
|
|
1116
|
+
beforeExecutionEffect(name, () => {
|
|
1117
|
+
if (!isDeepStrictEqual(current(), before))
|
|
1118
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1119
|
+
});
|
|
1120
|
+
// This address came from the same expected-generation native stamp, rather
|
|
1121
|
+
// than a reusable name. Claude's operation marker is read, never cleared.
|
|
1122
|
+
const result = tmux("send-keys", "-t", before.native.native_id, "Escape");
|
|
1123
|
+
if (result.code !== 0)
|
|
1124
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1125
|
+
return { schema: "aiterm.pty-key-result.v1", session_id: name, key: "Escape", outcome: "accepted",
|
|
1126
|
+
interruption: "unconfirmed", session_preserved: "unconfirmed" };
|
|
1127
|
+
}
|
|
1128
|
+
/** Private, read-only native record/identity evidence. This is deliberately not
|
|
1129
|
+
* an agent_stop result or finish proof. Even an exact native abort plus stable
|
|
1130
|
+
* observed identities does not establish the old invocation/parent-return
|
|
1131
|
+
* fence, or prevent a concurrent endpoint switch after these observations.
|
|
1132
|
+
*/
|
|
1133
|
+
export function observeExpectedAgentRunInterruption(input) {
|
|
1134
|
+
const point = executionPointSchema.parse(input), name = point.launch.session_id;
|
|
1135
|
+
if (point.boundary && !point.boundary.turn_id && !point.boundary.steer_id
|
|
1136
|
+
&& loadAgentMetadata(name).kind === "claude")
|
|
1137
|
+
return observeManagedClaudeInterruption(point);
|
|
1138
|
+
if (!point.boundary?.turn_id)
|
|
1139
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1140
|
+
const snapshot = () => {
|
|
1141
|
+
const generation = observeExecutionSessionGeneration(name), meta = loadAgentMetadata(name);
|
|
1142
|
+
if (!generation.exists || !generation.launch_id || !meta.execution_native)
|
|
1143
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1144
|
+
if (generation.launch_id !== point.launch.launch_id || meta.launch_id !== point.launch.launch_id)
|
|
1145
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1146
|
+
if (meta.kind !== "codex")
|
|
1147
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1148
|
+
const process = observeExecutionParentProcess(name);
|
|
1149
|
+
const actor = process?.harness_process, record = codexInterruptionRecord(meta);
|
|
1150
|
+
if (process?.schema !== "aiterm.execution-parent-process.v1" || !isDeepStrictEqual(process.source?.launch, point.launch)
|
|
1151
|
+
|| process.harness !== "codex-cli" || !actor || !Number.isSafeInteger(actor.pid) || actor.pid < 1
|
|
1152
|
+
|| !Number.isFinite(Date.parse(actor.started_identity)) || !record)
|
|
1153
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1154
|
+
const after = observeExecutionSessionGeneration(name), last = loadAgentMetadata(name);
|
|
1155
|
+
if (!after.exists || after.launch_id !== point.launch.launch_id || last.kind !== meta.kind
|
|
1156
|
+
|| !isDeepStrictEqual(last.execution_native, meta.execution_native))
|
|
1157
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1158
|
+
return { native: meta.execution_native, actor, record };
|
|
1159
|
+
};
|
|
1160
|
+
const before = snapshot(), after = snapshot();
|
|
1161
|
+
if (!isDeepStrictEqual(before, after))
|
|
1162
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1163
|
+
const boundary = after.record.boundary;
|
|
1164
|
+
const state = boundary.turn_id !== point.boundary.turn_id ? "other_run"
|
|
1165
|
+
: boundary.type === "turn_aborted" && boundary.reason === "interrupted" ? "interrupted"
|
|
1166
|
+
: boundary.type === "task_started" ? "running" : boundary.type === "task_complete" ? "completed" : "unconfirmed";
|
|
1167
|
+
return { schema: "aiterm.native-interruption-observation.v1", run: point,
|
|
1168
|
+
native: after.native, harness: "codex-cli", harness_process: after.actor,
|
|
1169
|
+
endpoint: { session_id: after.record.session_id, record_file: after.record.file },
|
|
1170
|
+
recorded_state: state, native_boundary: boundary,
|
|
1171
|
+
invocation_fence: "unknown", session_preserved: "unconfirmed" };
|
|
1172
|
+
}
|
|
1173
|
+
/** Existing Aiterm managed-operation provenance, not a native processing-start
|
|
1174
|
+
* or interruption proof. Reservation/receipt creation precedes input sending;
|
|
1175
|
+
* neither a marker nor its removal proves the CLI accepted or aborted it.
|
|
1176
|
+
* Keep the caller's original Point/cursor, never the current event-file size,
|
|
1177
|
+
* cached vendor conversation ID, latest job or another managed operation.
|
|
1178
|
+
*/
|
|
1179
|
+
function matchesClaudeInterruptionMarker(marker, point) {
|
|
1180
|
+
const boundary = point.boundary;
|
|
1181
|
+
return !!marker && !!boundary && boundary.turn_id === null && boundary.steer_id === null
|
|
1182
|
+
&& marker.operationId === boundary.operation_id
|
|
1183
|
+
&& (marker.operationId !== null || marker.eventCursor !== null)
|
|
1184
|
+
&& (marker.eventCursor === null || marker.eventCursor === boundary.event_cursor);
|
|
1185
|
+
}
|
|
1186
|
+
function observeManagedClaudeInterruption(point) {
|
|
1187
|
+
const name = point.launch.session_id, operation = point.boundary?.operation_id;
|
|
1188
|
+
if (!point.boundary || point.boundary.turn_id !== null || point.boundary.steer_id !== null)
|
|
1189
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1190
|
+
const files = (meta) => {
|
|
1191
|
+
const marker = agentClaudeOperationPath(name, point.launch.launch_id);
|
|
1192
|
+
const receipt = operation ? agentClaudeDispatchReceiptPath(name, point.launch.launch_id, operation) : null;
|
|
1193
|
+
const identity = (file, empty) => {
|
|
1194
|
+
const stat = fs.lstatSync(file, { bigint: true });
|
|
1195
|
+
if (!stat.isFile() || (empty && stat.size !== 0n) || !Number.isSafeInteger(Number(stat.dev)) || !Number.isSafeInteger(Number(stat.ino)))
|
|
1196
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1197
|
+
return { device: Number(stat.dev), inode: Number(stat.ino), size: stat.size, mtime: stat.mtimeNs, ctime: stat.ctimeNs };
|
|
1198
|
+
};
|
|
1199
|
+
try {
|
|
1200
|
+
const before = { marker: identity(marker, false), receipt: receipt ? identity(receipt, true) : null };
|
|
1201
|
+
if (!matchesClaudeInterruptionMarker(readClaudeOperationMarker(meta), point))
|
|
1202
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1203
|
+
const after = { marker: identity(marker, false), receipt: receipt ? identity(receipt, true) : null };
|
|
1204
|
+
if (!isDeepStrictEqual(before, after))
|
|
1205
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1206
|
+
return after;
|
|
1207
|
+
}
|
|
1208
|
+
catch (error) {
|
|
1209
|
+
if (error instanceof ExecutionError)
|
|
1210
|
+
throw error;
|
|
1211
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
const snapshot = () => {
|
|
1215
|
+
const generation = observeExecutionSessionGeneration(name), meta = loadAgentMetadata(name);
|
|
1216
|
+
if (!generation.exists || !generation.launch_id || meta.kind !== "claude" || !meta.execution_native)
|
|
1217
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1218
|
+
if (generation.launch_id !== point.launch.launch_id || meta.launch_id !== point.launch.launch_id)
|
|
1219
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1220
|
+
const beforeFiles = files(meta);
|
|
1221
|
+
const process = observeExecutionParentProcess(name);
|
|
1222
|
+
const actor = process?.harness_process;
|
|
1223
|
+
if (process?.schema !== "aiterm.execution-parent-process.v1" || !isDeepStrictEqual(process.source?.launch, point.launch)
|
|
1224
|
+
|| process.harness !== "claude-code" || !actor || !Number.isSafeInteger(actor.pid) || actor.pid < 1
|
|
1225
|
+
|| !Number.isFinite(Date.parse(actor.started_identity)))
|
|
1226
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1227
|
+
const after = observeExecutionSessionGeneration(name), last = loadAgentMetadata(name);
|
|
1228
|
+
if (!after.exists || after.launch_id !== point.launch.launch_id || last.kind !== meta.kind
|
|
1229
|
+
|| !isDeepStrictEqual(last.execution_native, meta.execution_native))
|
|
1230
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1231
|
+
const afterFiles = files(last);
|
|
1232
|
+
if (!isDeepStrictEqual(beforeFiles, afterFiles))
|
|
1233
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1234
|
+
return { native: meta.execution_native, actor, files: afterFiles };
|
|
1235
|
+
};
|
|
1236
|
+
const before = snapshot(), after = snapshot();
|
|
1237
|
+
if (!isDeepStrictEqual(before, after))
|
|
1238
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1239
|
+
const reference = (file) => ({ device: file.device, inode: file.inode });
|
|
1240
|
+
const common = { run: point, native: after.native, harness: "claude-code", harness_process: after.actor,
|
|
1241
|
+
recorded_state: "unconfirmed", invocation_fence: "unknown", session_preserved: "unconfirmed" };
|
|
1242
|
+
if (!operation)
|
|
1243
|
+
return { ...common, schema: "aiterm.claude-anonymous-interruption-observation.v1",
|
|
1244
|
+
endpoint: { session_id: null, operation_marker: reference(after.files.marker), dispatch_receipt: null },
|
|
1245
|
+
native_boundary: { type: "anonymous_turn_reserved", event_cursor: point.boundary.event_cursor },
|
|
1246
|
+
marker_protocol: "aiterm.claude-operation-marker.v2", loaded_hook: "unconfirmed" };
|
|
1247
|
+
return { ...common, schema: "aiterm.claude-managed-interruption-observation.v1",
|
|
1248
|
+
endpoint: { session_id: null, operation_marker: reference(after.files.marker), dispatch_receipt: reference(after.files.receipt) },
|
|
1249
|
+
native_boundary: { type: "managed_operation_reserved", operation_id: operation } };
|
|
1250
|
+
}
|
|
1251
|
+
/** Select the current native run for an explicit expected launch. The cursor
|
|
1252
|
+
* is the existing native rollout's task_started byte boundary, NOT an alias for a
|
|
1253
|
+
* dispatch/before_send receipt. Callers must retain that distinction; the
|
|
1254
|
+
* cascade controller reports missing canonical aliases rather than rekeying.
|
|
1255
|
+
* No metadata update, native key, latest-job lookup or cached vendor fallback.
|
|
1256
|
+
*/
|
|
1257
|
+
export function selectExpectedAgentRunInterruption(input, candidates) {
|
|
1258
|
+
const launch = executionPointSchema.shape.launch.parse(input), name = launch.session_id;
|
|
1259
|
+
const generation = observeExecutionSessionGeneration(name), meta = loadAgentMetadata(name);
|
|
1260
|
+
if (!generation.exists || !generation.launch_id)
|
|
1261
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1262
|
+
if (generation.launch_id !== launch.launch_id || meta.launch_id !== launch.launch_id)
|
|
1263
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1264
|
+
if (meta.kind === "claude") {
|
|
1265
|
+
const marker = readClaudeOperationMarker(meta);
|
|
1266
|
+
if (!marker)
|
|
1267
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1268
|
+
const references = executionPointSchema.array().parse(candidates ? Array.isArray(candidates) ? candidates : [candidates] : []);
|
|
1269
|
+
const point = references.find(value => isDeepStrictEqual(value.launch, launch) && matchesClaudeInterruptionMarker(marker, value));
|
|
1270
|
+
if (!point)
|
|
1271
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1272
|
+
// An existing operation receipt can supply its own canonical boundary.
|
|
1273
|
+
// Pick a matching recorded reference, not a latest job or inferred native
|
|
1274
|
+
// alias. Other work/Points do not veto Esc; unresolved aliases stay open.
|
|
1275
|
+
// This managed reservation is still unconfirmed, never native running.
|
|
1276
|
+
return observeManagedClaudeInterruption(point);
|
|
1277
|
+
}
|
|
1278
|
+
if (meta.kind !== "codex")
|
|
1279
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1280
|
+
const record = codexInterruptionRecord(meta);
|
|
1281
|
+
if (!record || record.boundary.type !== "task_started")
|
|
1282
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1283
|
+
const point = { launch, boundary: { event_cursor: record.event_cursor,
|
|
1284
|
+
turn_id: record.boundary.turn_id, operation_id: null, steer_id: null } };
|
|
1285
|
+
const observation = observeExpectedAgentRunInterruption(point);
|
|
1286
|
+
const last = codexInterruptionRecord(loadAgentMetadata(name));
|
|
1287
|
+
if (!isDeepStrictEqual(last, record) || observation.recorded_state !== "running")
|
|
1288
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1289
|
+
return observation;
|
|
971
1290
|
}
|
|
972
1291
|
// end 以下で最大の UTF-8 文字境界を返す(B3)。末尾が不完全なマルチバイト列なら、その開始位置まで
|
|
973
1292
|
// 戻す。pipe-pane が多バイト文字の途中でフラッシュした瞬間の増分読みで先頭/末尾が U+FFFD 化するのを防ぐ。
|
|
@@ -1328,6 +1647,56 @@ export function observeSession(name, cursor) {
|
|
|
1328
1647
|
};
|
|
1329
1648
|
return result;
|
|
1330
1649
|
}
|
|
1650
|
+
/** Internal generation admission read: same strict session listing/managed
|
|
1651
|
+
* metadata as observeSession, without screen or OS-process activity sampling.
|
|
1652
|
+
* No public tool is added. Call synchronously immediately before an effect.
|
|
1653
|
+
*/
|
|
1654
|
+
export function observeSessionGeneration(name) {
|
|
1655
|
+
assertSessionName(name);
|
|
1656
|
+
const exists = listSessionsResult().sessions.some(session => session.session_id === name);
|
|
1657
|
+
return { schema: "aiterm.pty-observe-result.v1", session_id: name, exists,
|
|
1658
|
+
launch_id: exists ? tryLoadAgentMetadata(name)?.launch_id ?? null : null };
|
|
1659
|
+
}
|
|
1660
|
+
/** Private guarded-runtime read. Legacy metadata without an original native
|
|
1661
|
+
* creator stamp remains a present but unverified generation, never enrolled
|
|
1662
|
+
* from a current same-name PID or cached vendor session.
|
|
1663
|
+
*/
|
|
1664
|
+
export function observeExecutionSessionGeneration(name) {
|
|
1665
|
+
assertSessionName(name);
|
|
1666
|
+
const result = (exists, launch_id) => ({ schema: "aiterm.pty-observe-result.v1",
|
|
1667
|
+
session_id: name, exists, launch_id });
|
|
1668
|
+
const before = observeExecutionNativeCreationScope();
|
|
1669
|
+
if (before.state !== "known")
|
|
1670
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED");
|
|
1671
|
+
const native = before.sessions.find(value => value.session_id === name);
|
|
1672
|
+
if (!native)
|
|
1673
|
+
return result(false, null);
|
|
1674
|
+
const meta = tryLoadAgentMetadata(name), proof = meta?.execution_native;
|
|
1675
|
+
if (!meta || !proof || !before.server || proof.native_id !== native.native_id
|
|
1676
|
+
|| !isDeepStrictEqual(proof.server, before.server))
|
|
1677
|
+
return result(true, null);
|
|
1678
|
+
const after = observeExecutionNativeCreationScope(), last = tryLoadAgentMetadata(name);
|
|
1679
|
+
if (after.state !== "known" || !isDeepStrictEqual(after.server, proof.server)
|
|
1680
|
+
|| after.sessions.find(value => value.session_id === name)?.native_id !== proof.native_id
|
|
1681
|
+
|| last?.launch_id !== meta.launch_id || !isDeepStrictEqual(last.execution_native, proof))
|
|
1682
|
+
return result(true, null);
|
|
1683
|
+
return result(true, meta.launch_id);
|
|
1684
|
+
}
|
|
1685
|
+
/** Private source-provenance snapshot. It deliberately omits screen, argv,
|
|
1686
|
+
* environment and cached vendor_session_id. No public tool is added. The
|
|
1687
|
+
* consumer proves its native serving MCP process descends from this unique
|
|
1688
|
+
* harness in this same process table, and separately verifies the endpoint.
|
|
1689
|
+
*/
|
|
1690
|
+
export function observeExecutionParentProcess(name) {
|
|
1691
|
+
assertSessionName(name);
|
|
1692
|
+
return readExecutionParentProcess(name, {
|
|
1693
|
+
present: () => listSessionsResult().sessions.some(session => session.session_id === name),
|
|
1694
|
+
metadata: () => tryLoadAgentMetadata(name),
|
|
1695
|
+
pane: () => tmux("display-message", "-p", "-t", name, "#{pane_pid}\t#{pane_dead}"),
|
|
1696
|
+
processes: readRuntimeProcesses,
|
|
1697
|
+
select: (meta, rows, pid) => ({ harness: agentHarness(meta.kind), processes: selectHarnessProcesses(meta, rows, processSubtree(rows, pid)) }),
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1331
1700
|
export function listSessionsResult(envKeys = []) {
|
|
1332
1701
|
for (const key of envKeys) {
|
|
1333
1702
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
@@ -1515,6 +1884,7 @@ function closeSessionInternal(name, observeDependency = true, o = {}) {
|
|
|
1515
1884
|
return `closed ${name}`;
|
|
1516
1885
|
}
|
|
1517
1886
|
export function closeSession(name) {
|
|
1887
|
+
assertExecutionContext(name);
|
|
1518
1888
|
return closeSessionInternal(name, true);
|
|
1519
1889
|
}
|
|
1520
1890
|
/**
|
|
@@ -1525,6 +1895,7 @@ export function closeSession(name) {
|
|
|
1525
1895
|
* already_closedとなり、close完了を文字列解析なしで確定できる。
|
|
1526
1896
|
*/
|
|
1527
1897
|
export function closeSessionResult(name) {
|
|
1898
|
+
assertExecutionContext(name);
|
|
1528
1899
|
assertSessionName(name);
|
|
1529
1900
|
const existed = sessionExists(name);
|
|
1530
1901
|
closeSessionInternal(name, true, { letHarnessExit: true });
|
|
@@ -1534,7 +1905,78 @@ export function closeSessionResult(name) {
|
|
|
1534
1905
|
outcome: existed ? "closed" : "already_closed",
|
|
1535
1906
|
};
|
|
1536
1907
|
}
|
|
1908
|
+
/** Cascade-stop adapter only: caller must already own the shared issued close
|
|
1909
|
+
* slot across every serving mutation path. No graceful idle wait, name-only
|
|
1910
|
+
* kill, cleanup of a replacement's metadata, or retry. Not publicly mounted.
|
|
1911
|
+
*/
|
|
1912
|
+
export function observeExecutionNativeSessions() {
|
|
1913
|
+
const result = tmux("list-sessions", "-F", "#{session_id}\t#{session_name}");
|
|
1914
|
+
if (result.code === 0)
|
|
1915
|
+
return parseExecutionNativeSessions(result.stdout);
|
|
1916
|
+
// A generic connection/list failure cannot prove absence.
|
|
1917
|
+
if (/^no server running on /i.test(result.stderr.trim()))
|
|
1918
|
+
return { state: "known", sessions: [] };
|
|
1919
|
+
return { state: "unknown" };
|
|
1920
|
+
}
|
|
1921
|
+
/** Existing POSIX server owner plus complete session listing. No screen,
|
|
1922
|
+
* cached generation, new process, server bootstrap or native mutation.
|
|
1923
|
+
*/
|
|
1924
|
+
export function observeExecutionNativeCreationScope() {
|
|
1925
|
+
if (isWin)
|
|
1926
|
+
return { state: "unknown" };
|
|
1927
|
+
try {
|
|
1928
|
+
const read = () => tmux("list-sessions", "-F", "#{pid}\t#{session_id}\t#{session_name}");
|
|
1929
|
+
const first = read();
|
|
1930
|
+
if (first.code !== 0)
|
|
1931
|
+
return /^no server running on /i.test(first.stderr.trim())
|
|
1932
|
+
? { state: "known", sessions: [], server: null } : { state: "unknown" };
|
|
1933
|
+
const rows = first.stdout.trim().split(/\r?\n/).filter(Boolean).map(line => line.split("\t"));
|
|
1934
|
+
if (!rows.length)
|
|
1935
|
+
return { state: "known", sessions: [], server: null };
|
|
1936
|
+
const pid = Number(rows[0][0]);
|
|
1937
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || rows.some(row => row.length !== 3 || row[0] !== String(pid)))
|
|
1938
|
+
return { state: "unknown" };
|
|
1939
|
+
const parsed = parseExecutionNativeSessions(rows.map(row => `${row[1]}\t${row[2]}`).join("\n"));
|
|
1940
|
+
if (parsed.state !== "known")
|
|
1941
|
+
return parsed;
|
|
1942
|
+
const before = readProcessIdentities([pid]), afterListing = read(), after = readProcessIdentities([pid]);
|
|
1943
|
+
if (before.length !== 1 || before[0].pid !== pid || !before[0].started_identity || after.length !== 1
|
|
1944
|
+
|| after[0].pid !== pid || after[0].started_identity !== before[0].started_identity
|
|
1945
|
+
|| afterListing.code !== 0 || afterListing.stdout !== first.stdout)
|
|
1946
|
+
return { state: "unknown" };
|
|
1947
|
+
return { ...parsed, server: { pid, started_identity: before[0].started_identity } };
|
|
1948
|
+
}
|
|
1949
|
+
catch {
|
|
1950
|
+
return { state: "unknown" };
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
/** Own the shared issued close slot before calling this primitive. */
|
|
1954
|
+
export function closeExpectedAgentSession(expected) {
|
|
1955
|
+
assertExecutionContext(expected.session_id);
|
|
1956
|
+
const initial = observeExecutionSessionGeneration(expected.session_id);
|
|
1957
|
+
if (initial.exists && initial.launch_id === null)
|
|
1958
|
+
throw new ExecutionError("EXECUTION_UNSUPPORTED");
|
|
1959
|
+
if (initial.exists && initial.launch_id !== expected.launch_id)
|
|
1960
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1961
|
+
closeExpectedExecution(expected, {
|
|
1962
|
+
sessions() {
|
|
1963
|
+
const current = observeExecutionSessionGeneration(expected.session_id);
|
|
1964
|
+
if (current.exists && current.launch_id !== expected.launch_id)
|
|
1965
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
1966
|
+
return observeExecutionNativeCreationScope();
|
|
1967
|
+
},
|
|
1968
|
+
launch(name) {
|
|
1969
|
+
const current = observeExecutionSessionGeneration(name);
|
|
1970
|
+
return current.exists && current.launch_id ? { session_id: name, launch_id: current.launch_id } : null;
|
|
1971
|
+
},
|
|
1972
|
+
kill(nativeId) {
|
|
1973
|
+
bindExecutionNativeTarget(expected.session_id, nativeId);
|
|
1974
|
+
return tmux("kill-session", "-t", nativeId);
|
|
1975
|
+
},
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1537
1978
|
export function killAll() {
|
|
1979
|
+
assertExecutionContext(null);
|
|
1538
1980
|
// agentsはAITERM_STATE_BASEを共有する別socketの席も含み得る。終了対象はこのsocketから確定する。
|
|
1539
1981
|
const sessions = new Set();
|
|
1540
1982
|
const listed = tmux("list-sessions", "-F", "#{session_name}");
|
|
@@ -1640,6 +2082,8 @@ function createAgentLineageContext(kind, sessionId, seed) {
|
|
|
1640
2082
|
}
|
|
1641
2083
|
export function runAgentApproval(options) {
|
|
1642
2084
|
const { action, session_id: name, approval_choice: selected, observed_prompt_digest: expected } = options;
|
|
2085
|
+
if (action === "respond")
|
|
2086
|
+
assertExecutionContext(name);
|
|
1643
2087
|
assertSessionName(name);
|
|
1644
2088
|
if (action === "inspect" && (selected !== undefined || expected !== undefined))
|
|
1645
2089
|
throw new AitermError("inspectでは承認選択とdigestを指定できません", 2);
|
|
@@ -1710,10 +2154,17 @@ let agentTuiReadyStableSamplesTestOverride = null;
|
|
|
1710
2154
|
function writeClaudeOperationMarker(meta, operationId) {
|
|
1711
2155
|
if (meta.kind !== "claude")
|
|
1712
2156
|
throw new AitermError("operation_id はClaude agent sessionだけで使用できます", 2);
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2157
|
+
const operation = operationId === null ? null : validateOperationId(operationId);
|
|
2158
|
+
// Only the same private request frame can supply this anonymous reference.
|
|
2159
|
+
// Unmounted/legacy execution keeps v1; never infer a cursor from file size.
|
|
2160
|
+
const captured = operation === null ? readExecutionSendBoundary(meta.aiterm_session) : null;
|
|
2161
|
+
if (captured && (!isDeepStrictEqual(captured.launch, { session_id: meta.aiterm_session, launch_id: meta.launch_id })
|
|
2162
|
+
|| !captured.boundary || captured.boundary.operation_id !== null || captured.boundary.turn_id !== null
|
|
2163
|
+
|| captured.boundary.steer_id !== null))
|
|
2164
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
2165
|
+
withExecutionPreparation(meta.aiterm_session, () => writeJson0600(agentClaudeOperationPath(meta.aiterm_session, meta.launch_id), captured ? {
|
|
2166
|
+
schema: "aiterm.claude-operation-marker.v2", operation_id: null, event_cursor: captured.boundary.event_cursor,
|
|
2167
|
+
} : { schema: "aiterm.claude-operation-marker.v1", operation_id: operation }));
|
|
1717
2168
|
}
|
|
1718
2169
|
function readClaudeOperationMarker(meta) {
|
|
1719
2170
|
if (meta.kind !== "claude")
|
|
@@ -1741,14 +2192,12 @@ function readClaudeOperationMarker(meta) {
|
|
|
1741
2192
|
catch {
|
|
1742
2193
|
throw new AitermError("Claude operation markerを読めません", 2);
|
|
1743
2194
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
(typeof value.operation_id !== "string" || !OPERATION_ID_RE.test(value.operation_id)))) {
|
|
2195
|
+
try {
|
|
2196
|
+
return parseClaudeOperationMarker(value);
|
|
2197
|
+
}
|
|
2198
|
+
catch {
|
|
1749
2199
|
throw new AitermError("Claude operation markerが不正です", 2);
|
|
1750
2200
|
}
|
|
1751
|
-
return { operationId: value.operation_id };
|
|
1752
2201
|
}
|
|
1753
2202
|
finally {
|
|
1754
2203
|
fs.closeSync(fd);
|
|
@@ -1892,6 +2341,8 @@ function assertExpectedClaudeOperation(meta, expectedOperationId) {
|
|
|
1892
2341
|
return active;
|
|
1893
2342
|
}
|
|
1894
2343
|
export function runClaudeApproval({ action, session_id: name, operation_id: operationIdInput, approval_choice: approvalChoice, observed_prompt_digest: observedPromptDigest, }) {
|
|
2344
|
+
if (action === "respond")
|
|
2345
|
+
assertExecutionContext(name);
|
|
1895
2346
|
assertSessionName(name);
|
|
1896
2347
|
if (!sessionExists(name))
|
|
1897
2348
|
throw new AitermError(`session '${name}' が無い`, 2);
|
|
@@ -2435,7 +2886,13 @@ function loadAgentMetadata(name) {
|
|
|
2435
2886
|
const deliveryFields = m.initial_prompt_delivery === undefined ? {} : {
|
|
2436
2887
|
initial_prompt_delivery: m.initial_prompt_delivery, initial_prompt_cursor: m.initial_prompt_cursor,
|
|
2437
2888
|
};
|
|
2438
|
-
const
|
|
2889
|
+
const parsedNative = m.execution_native === undefined ? null : executionNativeBindingSchema.safeParse(m.execution_native);
|
|
2890
|
+
if (parsedNative && !parsedNative.success)
|
|
2891
|
+
throw new AitermError("agent native bindingが不正です", 2);
|
|
2892
|
+
const executableFields = {
|
|
2893
|
+
...(m.agent_executable === undefined ? {} : { agent_executable: m.agent_executable }),
|
|
2894
|
+
...(parsedNative?.success ? { execution_native: parsedNative.data } : {}),
|
|
2895
|
+
};
|
|
2439
2896
|
// 席のファストの状態と、起動・設定替えで渡したmodel(ADR 0114)。この項目より前の版が書いた控えには無い。
|
|
2440
2897
|
const fastFields = {
|
|
2441
2898
|
...(typeof m.fast === "boolean" ? { fast: m.fast } : {}),
|
|
@@ -3421,7 +3878,11 @@ async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
|
|
|
3421
3878
|
else {
|
|
3422
3879
|
readyStreak = 0;
|
|
3423
3880
|
settledStreak = 0;
|
|
3424
|
-
|
|
3881
|
+
// The one keep-current key can be accepted before the TUI redraws.
|
|
3882
|
+
// Wait for that exact handled modal to disappear; never resend its key.
|
|
3883
|
+
const recoveringModal = opts.codexRateLimitRecovery && kind === "codex"
|
|
3884
|
+
&& codexRateLimitModelSwitchDialog(lastScreen) !== null;
|
|
3885
|
+
if (isAgentTuiActionRequired(kind, lastScreen) && !recoveringModal)
|
|
3425
3886
|
return { ready: false, samples, lastScreen };
|
|
3426
3887
|
}
|
|
3427
3888
|
if (now() >= deadline) {
|
|
@@ -3454,7 +3915,7 @@ function agentTurnSettled(meta) {
|
|
|
3454
3915
|
return false;
|
|
3455
3916
|
}
|
|
3456
3917
|
async function waitAgentTuiReady(name, meta, timeoutMs = AGENT_TUI_READY_TIMEOUT_MS, o = {}) {
|
|
3457
|
-
const result = await waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs, launchLine: agentLaunchLines.get(name), ...(o.settledTurn ? { turnSettled: () => agentTurnSettled(meta) } : {}) });
|
|
3918
|
+
const result = await waitAgentTuiReadyImpl(meta.kind, () => captureScreen(name, AGENT_TUI_READY_LINES), sleep, { timeoutMs, codexRateLimitRecovery: o.codexRateLimitRecovery, launchLine: agentLaunchLines.get(name), ...(o.settledTurn ? { turnSettled: () => agentTurnSettled(meta) } : {}) });
|
|
3458
3919
|
if (result.ready)
|
|
3459
3920
|
agentLaunchLines.delete(name);
|
|
3460
3921
|
return result;
|
|
@@ -3795,6 +4256,7 @@ async function prepareAgentInput(name, meta, options) {
|
|
|
3795
4256
|
return { status: "ready", reason: "composer_ready" };
|
|
3796
4257
|
}
|
|
3797
4258
|
export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
4259
|
+
assertExecutionContext(name);
|
|
3798
4260
|
assertSessionName(name);
|
|
3799
4261
|
loadAgentMetadata(name);
|
|
3800
4262
|
// 起動時promptの準備中に届いたpty_sendは、起動時promptを送り終えてから振り分ける(ADR 0088)。
|
|
@@ -3824,6 +4286,9 @@ async function sendInitialAgentPromptInLock(name, text, o) {
|
|
|
3824
4286
|
prepareSendText(promptText, { raw: false });
|
|
3825
4287
|
await o.before_send?.({ session_id: name, launch_id: meta.launch_id, vendor: meta.kind,
|
|
3826
4288
|
harness: agentHarness(meta.kind), event_cursor: startOffset, operation_id: null });
|
|
4289
|
+
captureExecutionSendBoundary({ launch: { session_id: name, launch_id: meta.launch_id }, boundary: {
|
|
4290
|
+
event_cursor: startOffset, operation_id: null, turn_id: null, steer_id: null
|
|
4291
|
+
} });
|
|
3827
4292
|
if (meta.kind === "claude") {
|
|
3828
4293
|
prepareSendText(text, { raw: false });
|
|
3829
4294
|
reserveAnonymousClaudeTurn(meta);
|
|
@@ -3972,11 +4437,11 @@ async function recoverCodexRateLimitModelSwitch(name, meta) {
|
|
|
3972
4437
|
async function waitAgentTuiReadyAfterCodexRateLimitRecovery(name, meta, timeoutMs, o = {}) {
|
|
3973
4438
|
let codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
|
|
3974
4439
|
// 上限のmodalを閉じた直後は、画面が落ち着くのを11回の確かめで待つ。
|
|
3975
|
-
let ready = await waitAgentTuiReady(name, meta, timeoutMs, codexRateLimitModelSwitch ? {} : o);
|
|
4440
|
+
let ready = await waitAgentTuiReady(name, meta, timeoutMs, codexRateLimitModelSwitch ? { codexRateLimitRecovery: true } : o);
|
|
3976
4441
|
if (!ready.ready && !codexRateLimitModelSwitch) {
|
|
3977
4442
|
codexRateLimitModelSwitch = await recoverCodexRateLimitModelSwitch(name, meta);
|
|
3978
4443
|
if (codexRateLimitModelSwitch)
|
|
3979
|
-
ready = await waitAgentTuiReady(name, meta, timeoutMs);
|
|
4444
|
+
ready = await waitAgentTuiReady(name, meta, timeoutMs, { codexRateLimitRecovery: true });
|
|
3980
4445
|
}
|
|
3981
4446
|
return { ready, codexRateLimitModelSwitch };
|
|
3982
4447
|
}
|
|
@@ -4069,6 +4534,8 @@ function launchAuthProcess(name, meta) {
|
|
|
4069
4534
|
* (別のアカウントへ入り直す時と、期限切れを状態から見抜けない時のため)。資格情報を消すことはせず、置き換えは公式CLIが行う。
|
|
4070
4535
|
*/
|
|
4071
4536
|
export async function authenticateAgent(kind, options) {
|
|
4537
|
+
if (options.action !== "status" || options.session_id !== undefined)
|
|
4538
|
+
assertExecutionContext(options.session_id);
|
|
4072
4539
|
const result = (status, session, message, pane) => ({ schema: "aiterm.agent-auth-result.v1", harness: agentHarness(kind),
|
|
4073
4540
|
status, session_id: session, url: pane?.url ?? null, user_code: pane?.user_code ?? null,
|
|
4074
4541
|
input_required: pane?.input_required ?? false, message: message ?? pane?.message ?? null });
|
|
@@ -4891,6 +5358,7 @@ async function applyLaunchFast(name, meta) {
|
|
|
4891
5358
|
}
|
|
4892
5359
|
}
|
|
4893
5360
|
export async function configureAgent(name, opts) {
|
|
5361
|
+
assertExecutionContext(name);
|
|
4894
5362
|
assertSessionName(name);
|
|
4895
5363
|
const model = opts.model?.trim() || null;
|
|
4896
5364
|
const effort = opts.reasoning_effort?.trim() || null;
|
|
@@ -4941,8 +5409,10 @@ async function configureAgentInLock(name, model, effort, fast) {
|
|
|
4941
5409
|
const rememberModel = () => {
|
|
4942
5410
|
if (!model)
|
|
4943
5411
|
return;
|
|
4944
|
-
|
|
4945
|
-
|
|
5412
|
+
withExecutionPreparation(name, () => {
|
|
5413
|
+
meta.model = model;
|
|
5414
|
+
writeAgentMetadata(meta);
|
|
5415
|
+
});
|
|
4946
5416
|
};
|
|
4947
5417
|
// fast: true は、modelやeffortを替える前に対応を確かめる(断る時は何も変えない)。
|
|
4948
5418
|
let fastVerified = false;
|
|
@@ -5222,6 +5692,7 @@ async function assertCursorPromptNotHookBlocked(name) {
|
|
|
5222
5692
|
} while (performance.now() < deadline || (performance.now() < hookDeadline && cursorPromptHooksRunning(screen)));
|
|
5223
5693
|
}
|
|
5224
5694
|
export async function dispatchAgentTurn(name, text, o = {}) {
|
|
5695
|
+
assertExecutionContext(name);
|
|
5225
5696
|
assertSessionName(name);
|
|
5226
5697
|
loadAgentMetadata(name);
|
|
5227
5698
|
return withAgentSendLock(name, () => dispatchAgentTurnInLock(name, text, o));
|
|
@@ -5309,6 +5780,9 @@ async function dispatchAgentTurnInLock(name, text, o) {
|
|
|
5309
5780
|
prepareSendText(dispatchText, { raw: o.raw });
|
|
5310
5781
|
await o.before_send?.({ session_id: name, launch_id: meta.launch_id, vendor: meta.kind,
|
|
5311
5782
|
harness: agentHarness(meta.kind), event_cursor: startOffset, operation_id: operationId });
|
|
5783
|
+
captureExecutionSendBoundary({ launch: { session_id: name, launch_id: meta.launch_id }, boundary: {
|
|
5784
|
+
event_cursor: startOffset, operation_id: operationId, turn_id: null, steer_id: null
|
|
5785
|
+
} });
|
|
5312
5786
|
recordInterimBoundary(meta, startOffset, operationId);
|
|
5313
5787
|
if (meta.kind === "claude") {
|
|
5314
5788
|
// durable/anonymousを分岐する前に同じsend preflightを通す。拒否されるpromptの
|
|
@@ -5462,6 +5936,7 @@ async function clearClaudeSelectMenuBeforeInput(name) {
|
|
|
5462
5936
|
* 同じsessionへの送信は1本ずつ通し、重なった後の1通は前の1通が終わってから振り分け直す(ADR 0088)。
|
|
5463
5937
|
*/
|
|
5464
5938
|
export async function sendAgentMessage(name, text, o = {}) {
|
|
5939
|
+
assertExecutionContext(name);
|
|
5465
5940
|
assertSessionName(name);
|
|
5466
5941
|
loadAgentMetadata(name);
|
|
5467
5942
|
// 前置きは、順番待ちにも入力受付の待ちにも入る前に確かめる(外れた時に待たせない)。
|
|
@@ -5596,6 +6071,8 @@ async function steerRunningTurn(name, meta, text, o) {
|
|
|
5596
6071
|
return receipt;
|
|
5597
6072
|
}
|
|
5598
6073
|
export async function runClaudeOperation({ session_id: name, action, operation_id: operationIdInput, text, before_send, }) {
|
|
6074
|
+
if (action === "issue")
|
|
6075
|
+
assertExecutionContext(name);
|
|
5599
6076
|
assertSessionName(name);
|
|
5600
6077
|
if (action !== "issue" && action !== "recover") {
|
|
5601
6078
|
throw new AitermError('action は "issue" または "recover" を指定してください', 2);
|
|
@@ -5802,6 +6279,7 @@ function existingAgentLaunchResult(kind, sid, model, effort) {
|
|
|
5802
6279
|
];
|
|
5803
6280
|
}
|
|
5804
6281
|
export function openAgent(kind, opts = {}) {
|
|
6282
|
+
assertExecutionContext(opts.session_name);
|
|
5805
6283
|
const label = agentLabel(kind);
|
|
5806
6284
|
// 前提検証は session を作る前に全部済ませる(失敗の残骸 session を作らない)。
|
|
5807
6285
|
// model/effort → bin → cwd の順: model/effort 検証は CLI 不在の端末でも同じ結果になる(テスト可能性)。
|
|
@@ -5942,23 +6420,37 @@ export function openAgent(kind, opts = {}) {
|
|
|
5942
6420
|
let launchNote = "";
|
|
5943
6421
|
try {
|
|
5944
6422
|
const lineageContext = createAgentLineageContext(kind, sid, lineageSeed);
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
meta
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
6423
|
+
if (hasExecutionCreationScope())
|
|
6424
|
+
beforeExecutionEffect(sid);
|
|
6425
|
+
const meta = withExecutionPreparation(sid, () => {
|
|
6426
|
+
const meta = agentDone
|
|
6427
|
+
? kind === "claude"
|
|
6428
|
+
? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest, lineageContext, model, effort)
|
|
6429
|
+
: kind === "codex"
|
|
6430
|
+
? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort }, writeScope, lineageContext)
|
|
6431
|
+
: kind === "cursor"
|
|
6432
|
+
? createCursorAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", writeScope, lineageContext)
|
|
6433
|
+
: createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none", grokAuthPath, writeScope, lineageContext)
|
|
6434
|
+
: null;
|
|
6435
|
+
if (meta) {
|
|
6436
|
+
// Metadata constructors publish their internally generated launch ID.
|
|
6437
|
+
// Stamp its exact native creator before binding and any later input.
|
|
6438
|
+
if (hasExecutionCreationScope())
|
|
6439
|
+
meta.execution_native = executionCreatedNativeBinding(sid);
|
|
6440
|
+
meta.agent_executable = bin;
|
|
6441
|
+
// 席は通常の速度で始まる(Claude Code・Codexは起動の引数で明示する)。fast版のmodel IDで起こした時だけtrue。
|
|
6442
|
+
meta.fast = opts.fast_state === true;
|
|
6443
|
+
meta.model = opts.base_model !== undefined ? opts.base_model : model;
|
|
6444
|
+
// The new physical shell is already pinned by its actual create receipt.
|
|
6445
|
+
// Validate it before publishing metadata, then bind this internally
|
|
6446
|
+
// generated managed launch. Model arguments cannot select this ID.
|
|
6447
|
+
writeAgentMetadata(meta);
|
|
6448
|
+
agentMetadataNegativeCache.delete(sid);
|
|
6449
|
+
}
|
|
6450
|
+
return meta;
|
|
6451
|
+
});
|
|
6452
|
+
if (meta && hasExecutionCreationScope())
|
|
6453
|
+
bindExecutionCreatedLaunch({ session_id: sid, launch_id: meta.launch_id });
|
|
5962
6454
|
launchNote = buildAgentLaunchNote(kind, model, effort, meta);
|
|
5963
6455
|
let full;
|
|
5964
6456
|
if (paneShell === "pwsh" && meta) {
|
|
@@ -5982,7 +6474,7 @@ export function openAgent(kind, opts = {}) {
|
|
|
5982
6474
|
});
|
|
5983
6475
|
}
|
|
5984
6476
|
catch (e) {
|
|
5985
|
-
const failure = telemetryOwnedFailure("AITERM.VENDOR_LAUNCHER_FAILED", e);
|
|
6477
|
+
const failure = e instanceof ExecutionError ? e : telemetryOwnedFailure("AITERM.VENDOR_LAUNCHER_FAILED", e);
|
|
5986
6478
|
// 起動コマンドを投入できなかった session は空のまま残る=残骸を作らない。片付けてから元エラーを伝える。
|
|
5987
6479
|
try {
|
|
5988
6480
|
closeSessionInternal(sid, false);
|
|
@@ -6020,6 +6512,7 @@ export class AgentLaunchPromptError extends AitermError {
|
|
|
6020
6512
|
}
|
|
6021
6513
|
}
|
|
6022
6514
|
export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
6515
|
+
assertExecutionContext(opts.session_name);
|
|
6023
6516
|
const mission = opts.prompt ?? null;
|
|
6024
6517
|
const sourceSessionId = opts.throughline_source_session ?? null;
|
|
6025
6518
|
const supplementFile = opts.throughline_supplement_file ?? null;
|