aiterm-mcp 0.27.8 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +46 -40
- package/README.md +45 -41
- package/dist/agent-resolver.js +14 -5
- package/dist/agent-shared.js +18 -5
- package/dist/aiterm-wait-cli.js +1 -1
- package/dist/core.js +175 -25
- package/dist/index.js +112 -59
- package/dist/tmux-runtime.js +12 -0
- package/dist/vendors/codex.js +2 -1
- package/dist/vendors/cursor.js +393 -0
- package/dist/vendors/grok.js +2 -1
- package/package.json +2 -2
package/dist/agent-resolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// エージェント CLI(claude / codex / grok)・Throughline・pane shell(Windows は Git Bash)の
|
|
1
|
+
// エージェント CLI(claude / codex / grok / cursor-agent)・Throughline・pane shell(Windows は Git Bash)の
|
|
2
2
|
// 実行ファイルをどう見つけ、どう起動するかの所有者(OS 分岐の所有者。tmux とは独立)。
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import * as fs from "node:fs";
|
|
@@ -115,7 +115,9 @@ export function resolveAgentBin(kind) {
|
|
|
115
115
|
? ["CLAUDE_BIN", [".local", "bin", "claude"], "claude"]
|
|
116
116
|
: kind === "codex"
|
|
117
117
|
? ["CODEX_BIN", [".local", "bin", "codex"], "codex"]
|
|
118
|
-
:
|
|
118
|
+
: kind === "cursor"
|
|
119
|
+
? ["CURSOR_AGENT_BIN", [".local", "bin", "cursor-agent"], "cursor-agent"]
|
|
120
|
+
: ["GROK_BIN", [".grok", "bin", isWin ? "grok.exe" : "grok"], "grok"];
|
|
119
121
|
const fromEnv = process.env[envVar];
|
|
120
122
|
if (fromEnv) {
|
|
121
123
|
// 明示指定 env は実在を検証する。存在しないパスを黙って返すと、session を作って
|
|
@@ -125,9 +127,16 @@ export function resolveAgentBin(kind) {
|
|
|
125
127
|
return resolveWindowsCodexShim(kind, fromEnv);
|
|
126
128
|
throw new AitermError(`${envVar} に指定された ${name} が存在しません: ${fromEnv}`, 2);
|
|
127
129
|
}
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
130
|
+
const defaultCandidates = [path.join(home, ...rel)];
|
|
131
|
+
if (kind === "cursor" && isWin && process.env.LOCALAPPDATA) {
|
|
132
|
+
// Cursor公式Windows installerは %LOCALAPPDATA%\cursor-agent をPATHへ追加し、
|
|
133
|
+
// cursor-agent.exe/cmdを置く。GUI hostの古いPATHでも公式配置を直接解決する。
|
|
134
|
+
defaultCandidates.unshift(path.join(process.env.LOCALAPPDATA, "cursor-agent", "cursor-agent.exe"), path.join(process.env.LOCALAPPDATA, "cursor-agent", "cursor-agent.cmd"));
|
|
135
|
+
}
|
|
136
|
+
for (const cand of defaultCandidates) {
|
|
137
|
+
if (isUsableAgentExecutableFile(cand))
|
|
138
|
+
return resolveWindowsCodexShim(kind, cand);
|
|
139
|
+
}
|
|
131
140
|
const w = spawnSync(isWin ? "where" : "which", [name], {
|
|
132
141
|
encoding: "utf8",
|
|
133
142
|
timeout: 5000,
|
package/dist/agent-shared.js
CHANGED
|
@@ -164,7 +164,18 @@ export function agentLabel(kind) {
|
|
|
164
164
|
? "Grok Build(Composer)"
|
|
165
165
|
: kind === "grok"
|
|
166
166
|
? "Grok Build(Grok)"
|
|
167
|
-
: "
|
|
167
|
+
: kind === "cursor"
|
|
168
|
+
? "Cursor Agent CLI"
|
|
169
|
+
: "Codex";
|
|
170
|
+
}
|
|
171
|
+
export function agentHarness(kind) {
|
|
172
|
+
return kind === "claude"
|
|
173
|
+
? "claude-code"
|
|
174
|
+
: kind === "codex"
|
|
175
|
+
? "codex-cli"
|
|
176
|
+
: kind === "cursor"
|
|
177
|
+
? "cursor-cli"
|
|
178
|
+
: "grok-cli";
|
|
168
179
|
}
|
|
169
180
|
export function subagentInstruction(meta) {
|
|
170
181
|
if (meta.agent_role !== "subagent" ||
|
|
@@ -193,10 +204,12 @@ export function subagentInstruction(meta) {
|
|
|
193
204
|
export function writeScopeLaunchNote(kind, writeScope) {
|
|
194
205
|
return writeScope === undefined
|
|
195
206
|
? ""
|
|
196
|
-
:
|
|
197
|
-
? `\n能力宣言: write_scope=${JSON.stringify(writeScope)}
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
: kind === "cursor" && writeScope === "read-only"
|
|
208
|
+
? `\n能力宣言: write_scope=${JSON.stringify(writeScope)}。Cursor Agent CLIへ --mode ask を付与し、書込みを実効禁止。`
|
|
209
|
+
: (kind === "codex" || kind === "grok" || kind === "composer") && writeScope === "read-only"
|
|
210
|
+
? `\n能力宣言: write_scope=${JSON.stringify(writeScope)}。${agentLabel(kind)} CLIへ --sandbox read-only を付与し、書込みを実効禁止。` +
|
|
211
|
+
(kind === "codex" ? "" : "MCPツール許可は --always-approve で自動承認(sandbox内のため能力拡大なし)。")
|
|
212
|
+
: `\n能力宣言: write_scope=${JSON.stringify(writeScope)}。パス単位のsandbox allowlistに対応するCLI引数がないため宣言の記録のみ(構造的unsupported)。`;
|
|
200
213
|
}
|
|
201
214
|
export function agentLineageFields(context) {
|
|
202
215
|
return {
|
package/dist/aiterm-wait-cli.js
CHANGED
|
@@ -42,7 +42,7 @@ export function parseArgs(argv) {
|
|
|
42
42
|
if (cursor !== null)
|
|
43
43
|
throw new Error(`--cursor が重複しています。${USAGE}`);
|
|
44
44
|
if (!/^\d+$/.test(v))
|
|
45
|
-
throw new Error(`--cursor は0
|
|
45
|
+
throw new Error(`--cursor は0以上の整数完了境界だけを受理します。${USAGE}`);
|
|
46
46
|
cursor = Number(v);
|
|
47
47
|
}
|
|
48
48
|
else {
|
package/dist/core.js
CHANGED
|
@@ -13,14 +13,16 @@ import * as path from "node:path";
|
|
|
13
13
|
import { createHash, randomBytes } from "node:crypto";
|
|
14
14
|
import * as rtk from "./rtk.js";
|
|
15
15
|
import { AitermError, telemetryOwnedFailure, ownTelemetryFailure } from "./errors.js";
|
|
16
|
-
import { isWin, SOCKDIR, tmuxCommand, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
17
|
-
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentLineageFields, } from "./agent-shared.js";
|
|
16
|
+
import { isWin, SOCKDIR, tmuxCommand, loadPtyBufferChunk, pasteBufferBaseArgs, TMUX_EMPTY_CONFIG, attachCommand, normalizePaneCommand, appendMarkSentinel, settlePaneLog, paneCwdArgument, } from "./tmux-runtime.js";
|
|
17
|
+
import { sleep, currentUid, runtimeStateBase, safeStatSize, readFileRange, writeJson0600, createEmpty0600, shq, LAUNCH_ID_RE, AGENT_DONE_POLL_MS, AGENT_EVENT_MAX_BYTES, assertSessionName, agentsDir, agentEventPath, agentMetadataPath, writeAgentMetadata, AGENT_EVENT_TAIL_BYTES, agentLabel, agentHarness, subagentInstruction, agentLineageFields, } from "./agent-shared.js";
|
|
18
18
|
import { GROK_MODEL_DEFAULTS, realGrokHome, resolveAndValidateGrokAuth, assertGrokModelAvailable, grokEventsTranscript, latestGrokCompletion, grokInitializationComplete, observeGrokDone, buildGrokAgentCmd, grokLaunchNote, grokEnvTokens, grokTuiReady, GROK_COMPOSER_MARKER_RE, grokFooterHasConfiguration, grokTranscriptText, createGrokAgentMetadata, } from "./vendors/grok.js";
|
|
19
19
|
import { bindCodexTranscriptSession, latestCodexCompletion, observeCodexDone, buildCodexAgentCmd, codexLaunchNote, codexTuiReady, CODEX_COMPOSER_MARKER_RE, codexModelChoice, codexEffortChoice, codexMoreReasoningChoice, codexTranscriptText, createCodexAgentMetadata, } from "./vendors/codex.js";
|
|
20
20
|
import { OPERATION_ID_RE, CLAUDE_RESULT_MAX_BYTES, CLAUDE_EFFORTS, agentManagedClaudeSettingsPath, agentClaudeResultPath, agentClaudeOperationPath, agentClaudeApprovalReceiptPath, agentClaudeDispatchReceiptPath, validateOperationId, readClaudeResultText, assertClaudeAuthenticationReady, buildClaudeAgentCmd, claudeLaunchNote, claudeTuiReady, CLAUDE_COMPOSER_MARKER_RE, createClaudeAgentMetadata, } from "./vendors/claude.js";
|
|
21
|
+
import { bindCursorTranscriptSession, cursorTurnBoundary, latestCursorCompletion, observeCursorDone, cursorTranscriptText, assertCursorAuthenticationReady, assertCursorModelAvailable, buildCursorAgentCmd, createCursorAgentMetadata, cursorLaunchNote, cursorEffortNavigation, cursorTuiReady, CURSOR_COMPOSER_MARKER_RE, validateCursorModelEffort, } from "./vendors/cursor.js";
|
|
21
22
|
import { resolveAgentBin, resolveThroughlineBin, runThroughlineHandoffContext, isWindowsNativeExecutable, agentBinForPaneShell, resolveWinPaneShell } from "./agent-resolver.js";
|
|
22
23
|
export { AitermError } from "./errors.js";
|
|
23
24
|
export { tmuxSpawnEnv } from "./tmux-runtime.js";
|
|
25
|
+
export { agentHarness } from "./agent-shared.js";
|
|
24
26
|
// 完了検出
|
|
25
27
|
export const DEFAULT_TIMEOUT = 10.0;
|
|
26
28
|
const POLL = 0.25;
|
|
@@ -202,11 +204,12 @@ function cleanupAgentState(name) {
|
|
|
202
204
|
f.endsWith(".claude-settings.json") ||
|
|
203
205
|
f.endsWith(".claude-mcp.json") ||
|
|
204
206
|
f.endsWith(".claude-result.json") ||
|
|
207
|
+
f.endsWith(".cursor-result.json") ||
|
|
205
208
|
f.endsWith(".claude-operation.json") ||
|
|
206
209
|
f.endsWith(".claude-approval.json") ||
|
|
207
210
|
f.endsWith(".claude-dispatch"))
|
|
208
211
|
fs.unlinkSync(p);
|
|
209
|
-
else if (f.endsWith(".codex-home") || f.endsWith(".grok-home") || f.endsWith(".home")) {
|
|
212
|
+
else if (f.endsWith(".codex-home") || f.endsWith(".grok-home") || f.endsWith(".cursor-plugin") || f.endsWith(".home")) {
|
|
210
213
|
fs.rmSync(p, { recursive: true, force: true });
|
|
211
214
|
}
|
|
212
215
|
}
|
|
@@ -651,10 +654,11 @@ export function send(name, text, o = {}) {
|
|
|
651
654
|
assertInitialPromptNotPendingForSend(name, !!o.force);
|
|
652
655
|
assertManagedClaudeCredentialCommandNotSent(name, text);
|
|
653
656
|
text = prepareSendText(text, o);
|
|
657
|
+
let fg = "";
|
|
654
658
|
if (o.mark) {
|
|
655
659
|
// mark の sentinel は POSIX シェル構文。前面が fish/csh/tcsh 等の非 POSIX 対話シェルだと "$?" が
|
|
656
660
|
// 壊れて sentinel が成立しない。黙って壊れた完了検出を作らず、明示エラーで until を促す(B8)。
|
|
657
|
-
|
|
661
|
+
fg = paneCurrentCommand(name);
|
|
658
662
|
if (NON_POSIX_MARK_SHELLS.has(fg)) {
|
|
659
663
|
throw new AitermError(`mark は POSIX シェル(bash/sh/zsh/dash)前提です。前面が ${fg} のため sentinel の "$?" が` +
|
|
660
664
|
`正しく展開されません。until で完了パターンを指定してください。`, 2);
|
|
@@ -675,7 +679,7 @@ export function send(name, text, o = {}) {
|
|
|
675
679
|
if (o.rtk && !o.force)
|
|
676
680
|
assertNotDestructive(text, 3, "rtk 変換後: ");
|
|
677
681
|
if (o.mark)
|
|
678
|
-
text = text
|
|
682
|
+
text = appendMarkSentinel(text, fg);
|
|
679
683
|
assertSendTextSize(text, o.rtk || o.mark ? "変換後の送信文字列" : "送信文字列");
|
|
680
684
|
const reportedText = text;
|
|
681
685
|
if (!o.raw && text.includes("\n") && ATOMIC_MULTILINE_SHELLS.has(paneCurrentCommand(name))) {
|
|
@@ -922,6 +926,7 @@ export function listSessions() {
|
|
|
922
926
|
return line;
|
|
923
927
|
const agent = [
|
|
924
928
|
`agent=${meta.kind}`,
|
|
929
|
+
`harness=${agentHarness(meta.kind)}`,
|
|
925
930
|
"agent_done=true",
|
|
926
931
|
meta.write_scope === undefined ? null : `write_scope=${JSON.stringify(meta.write_scope)}`,
|
|
927
932
|
meta.vendor_session_id ? `vendor_session_id=${meta.vendor_session_id}` : null,
|
|
@@ -1583,7 +1588,7 @@ function loadAgentMetadata(name) {
|
|
|
1583
1588
|
throw new AitermError(`agent metadata を読めません: ${e.message}`, 2);
|
|
1584
1589
|
}
|
|
1585
1590
|
const m = raw;
|
|
1586
|
-
if ((m.kind !== "claude" && m.kind !== "codex" && m.kind !== "grok" && m.kind !== "composer") ||
|
|
1591
|
+
if ((m.kind !== "claude" && m.kind !== "codex" && m.kind !== "grok" && m.kind !== "composer" && m.kind !== "cursor") ||
|
|
1587
1592
|
m.aiterm_session !== name ||
|
|
1588
1593
|
typeof m.launch_id !== "string" ||
|
|
1589
1594
|
!LAUNCH_ID_RE.test(m.launch_id)) {
|
|
@@ -1654,6 +1659,31 @@ function loadAgentMetadata(name) {
|
|
|
1654
1659
|
codex_home: recordedHome,
|
|
1655
1660
|
};
|
|
1656
1661
|
}
|
|
1662
|
+
if (m.kind === "cursor") {
|
|
1663
|
+
const recordedHome = m.cursor_home;
|
|
1664
|
+
if (m.hook_route !== "shared_cursor_home" ||
|
|
1665
|
+
m.completion_route !== "cursor_transcript" ||
|
|
1666
|
+
typeof recordedHome !== "string" ||
|
|
1667
|
+
!path.isAbsolute(recordedHome)) {
|
|
1668
|
+
throw new AitermError("agent metadata の cursor_home が不正です(絶対パスの記録が必要)", 2);
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
kind: "cursor",
|
|
1672
|
+
aiterm_session: name,
|
|
1673
|
+
launch_id: m.launch_id,
|
|
1674
|
+
event_file: expectedEvent,
|
|
1675
|
+
created_at: typeof m.created_at === "string" ? m.created_at : "",
|
|
1676
|
+
cwd: typeof m.cwd === "string" ? m.cwd : null,
|
|
1677
|
+
...(typeof m.write_scope === "string" ? { write_scope: m.write_scope } : {}),
|
|
1678
|
+
vendor_session_id: typeof m.vendor_session_id === "string" ? m.vendor_session_id : null,
|
|
1679
|
+
initial_prompt: normalizeInitialPromptState(m.initial_prompt),
|
|
1680
|
+
hook_route: "shared_cursor_home",
|
|
1681
|
+
completion_route: "cursor_transcript",
|
|
1682
|
+
...loadAgentLineageFields(m, true),
|
|
1683
|
+
node_platform: process.platform,
|
|
1684
|
+
cursor_home: recordedHome,
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1657
1687
|
// grok_home も codex_home と同じ理由で per-launch の記録値を正とする(席専用 GROK_HOME)。
|
|
1658
1688
|
// 認証正本の照合も記録された home に対して行う——現在の env の home と比較すると、
|
|
1659
1689
|
// 席専用 home で起動した session が別 instance から拒否される。
|
|
@@ -1790,6 +1820,15 @@ function bindCompletedInitialPrompt(meta) {
|
|
|
1790
1820
|
setInitialPromptState(meta, "done");
|
|
1791
1821
|
return;
|
|
1792
1822
|
}
|
|
1823
|
+
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
1824
|
+
const done = latestCursorCompletion(meta, readTranscriptLines);
|
|
1825
|
+
if (!done) {
|
|
1826
|
+
throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。${agentWaitGuide(meta.aiterm_session)}`, 2);
|
|
1827
|
+
}
|
|
1828
|
+
bindAgentVendorSession(meta, done);
|
|
1829
|
+
setInitialPromptState(meta, "done");
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1793
1832
|
if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
|
|
1794
1833
|
if (!latestGrokCompletion(meta, readTranscriptLines)) {
|
|
1795
1834
|
throw new AitermError(`agent session '${meta.aiterm_session}' は起動時 prompt の完了待ちです。${agentWaitGuide(meta.aiterm_session)}`, 2);
|
|
@@ -1827,6 +1866,9 @@ function tryLoadAgentMetadata(name) {
|
|
|
1827
1866
|
function latestAgentDoneEvent(meta, expectedOperationId = null) {
|
|
1828
1867
|
if (meta.kind === "codex")
|
|
1829
1868
|
return latestCodexCompletion(meta, readTranscriptLines);
|
|
1869
|
+
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
1870
|
+
return latestCursorCompletion(meta, readTranscriptLines);
|
|
1871
|
+
}
|
|
1830
1872
|
if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
|
|
1831
1873
|
return latestGrokCompletion(meta, readTranscriptLines);
|
|
1832
1874
|
}
|
|
@@ -1892,6 +1934,14 @@ function recoverAgentVendorSession(meta) {
|
|
|
1892
1934
|
setInitialPromptState(meta, "done");
|
|
1893
1935
|
return;
|
|
1894
1936
|
}
|
|
1937
|
+
if (meta.kind === "cursor") {
|
|
1938
|
+
const transcript = bindCursorTranscriptSession(meta);
|
|
1939
|
+
if (!transcript)
|
|
1940
|
+
return;
|
|
1941
|
+
if (meta.initial_prompt === "pending" && latestCursorCompletion(meta, readTranscriptLines))
|
|
1942
|
+
setInitialPromptState(meta, "done");
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1895
1945
|
const size = safeStatSize(meta.event_file);
|
|
1896
1946
|
if (size === 0)
|
|
1897
1947
|
return;
|
|
@@ -1915,6 +1965,10 @@ function agentCompletionCursor(meta) {
|
|
|
1915
1965
|
const transcript = grokEventsTranscript(meta);
|
|
1916
1966
|
return transcript ? safeStatSize(transcript) : 0;
|
|
1917
1967
|
}
|
|
1968
|
+
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
1969
|
+
bindCursorTranscriptSession(meta);
|
|
1970
|
+
return cursorTurnBoundary(meta);
|
|
1971
|
+
}
|
|
1918
1972
|
if (meta.kind !== "codex")
|
|
1919
1973
|
return safeStatSize(meta.event_file);
|
|
1920
1974
|
const transcript = bindCodexTranscriptSession(meta);
|
|
@@ -1928,7 +1982,7 @@ function transcriptUnavailable() {
|
|
|
1928
1982
|
throw new AitermError(`transcript がまだありません。ターン完了後に再取得してください。${agentWaitGuide()}`, 2);
|
|
1929
1983
|
}
|
|
1930
1984
|
function transcriptNotFound(vendor) {
|
|
1931
|
-
throw new AitermError(`最終 assistant メッセージを特定できませんでした(vendor=${vendor})。screen で確認してください。`, 2);
|
|
1985
|
+
throw new AitermError(`最終 assistant メッセージを特定できませんでした(harness=${agentHarness(vendor)}, vendor=${vendor})。screen で確認してください。`, 2);
|
|
1932
1986
|
}
|
|
1933
1987
|
function readTranscriptLines(file) {
|
|
1934
1988
|
try {
|
|
@@ -2000,6 +2054,9 @@ export async function readAgentTranscript(name, o = {}) {
|
|
|
2000
2054
|
transcriptUnavailable();
|
|
2001
2055
|
text = readClaudeResultText(meta, done, operationId, transcriptUnavailable);
|
|
2002
2056
|
}
|
|
2057
|
+
else if (meta.kind === "cursor") {
|
|
2058
|
+
text = cursorTranscriptText(meta, readTranscriptLines, transcriptUnavailable);
|
|
2059
|
+
}
|
|
2003
2060
|
else if (meta.kind === "codex") {
|
|
2004
2061
|
text = codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable);
|
|
2005
2062
|
}
|
|
@@ -2016,6 +2073,7 @@ export async function readAgentTranscript(name, o = {}) {
|
|
|
2016
2073
|
"agent_transcript",
|
|
2017
2074
|
`vendor=${meta.kind}`,
|
|
2018
2075
|
`turn_id=${turnId ?? "unknown"}`,
|
|
2076
|
+
`harness=${agentHarness(meta.kind)}`,
|
|
2019
2077
|
done?.operation_id ? `operation_id=${done.operation_id}` : null,
|
|
2020
2078
|
`raw_chars=${rawChars}`,
|
|
2021
2079
|
].filter(Boolean).join(" ");
|
|
@@ -2053,6 +2111,7 @@ function agentReadMetadataSuffix(name, screen) {
|
|
|
2053
2111
|
const bits = [
|
|
2054
2112
|
"agent",
|
|
2055
2113
|
`vendor=${meta.kind}`,
|
|
2114
|
+
`harness=${agentHarness(meta.kind)}`,
|
|
2056
2115
|
`initial_prompt=${meta.initial_prompt}`,
|
|
2057
2116
|
`agent_event_seen=${ev ? "true" : "false"}`,
|
|
2058
2117
|
"completion_attribution=none",
|
|
@@ -2144,7 +2203,7 @@ export function detectAgentRateLimit(kind, aitermSession) {
|
|
|
2144
2203
|
return null;
|
|
2145
2204
|
}
|
|
2146
2205
|
// 外部waiterプロセス用の純リーダー観測。lock・PTY・metadata書込・dispatch状態には一切触れない。
|
|
2147
|
-
// Codex
|
|
2206
|
+
// Codex/Cursor/Grokは各harnessのtranscript、Claudeはevent fileを増分走査し、vendor_session_idのbind永続化を
|
|
2148
2207
|
// 行わない(waiterは観測者であって所有者でない)。
|
|
2149
2208
|
export async function observeAgentDone(name, o = {}) {
|
|
2150
2209
|
const meta = loadAgentMetadata(name);
|
|
@@ -2153,12 +2212,15 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2153
2212
|
throw new AitermError("operation_id はClaude agent sessionだけで使用できます", 2);
|
|
2154
2213
|
}
|
|
2155
2214
|
if (o.cursor != null && (!Number.isInteger(o.cursor) || o.cursor < 0)) {
|
|
2156
|
-
throw new AitermError("cursor は0
|
|
2215
|
+
throw new AitermError("cursor は0以上の整数完了境界で指定してください", 2);
|
|
2157
2216
|
}
|
|
2158
2217
|
const timeout = o.timeout ?? DEFAULT_AGENT_DONE_TIMEOUT;
|
|
2159
2218
|
if (meta.kind === "codex" && meta.completion_route === "codex_transcript") {
|
|
2160
2219
|
return observeCodexDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2161
2220
|
}
|
|
2221
|
+
if (meta.kind === "cursor" && meta.completion_route === "cursor_transcript") {
|
|
2222
|
+
return observeCursorDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2223
|
+
}
|
|
2162
2224
|
if ((meta.kind === "grok" || meta.kind === "composer") && meta.completion_route === "grok_transcript") {
|
|
2163
2225
|
return observeGrokDone(meta, timeout, o.cursor, detectAgentRateLimit);
|
|
2164
2226
|
}
|
|
@@ -2175,6 +2237,7 @@ export async function observeAgentDone(name, o = {}) {
|
|
|
2175
2237
|
session_id: meta.aiterm_session,
|
|
2176
2238
|
launch_id: meta.launch_id,
|
|
2177
2239
|
vendor: meta.kind,
|
|
2240
|
+
harness: agentHarness(meta.kind),
|
|
2178
2241
|
outcome,
|
|
2179
2242
|
operation_id: ev?.operation_id ?? operationId,
|
|
2180
2243
|
vendor_session_id: ev?.vendor_session_id ?? meta.vendor_session_id ?? null,
|
|
@@ -2224,19 +2287,27 @@ function isAgentTuiReady(kind, screen) {
|
|
|
2224
2287
|
return claudeTuiReady(screen);
|
|
2225
2288
|
if (kind === "codex")
|
|
2226
2289
|
return codexTuiReady(screen);
|
|
2290
|
+
if (kind === "cursor")
|
|
2291
|
+
return cursorTuiReady(screen);
|
|
2227
2292
|
return grokTuiReady(screen);
|
|
2228
2293
|
}
|
|
2229
|
-
// Codex/Claude は実行中に「(esc to interrupt)
|
|
2230
|
-
//
|
|
2294
|
+
// Codex/Claude は実行中に「(esc to interrupt)」、Cursor は「Working」+
|
|
2295
|
+
// 「ctrl+c to stop」を表示する(いずれも実機採取)。startup 側の処理(MCP initialize 等)が
|
|
2296
|
+
// 走ったまま古い composer がscrollbackに残る画面は入力受付とみなさない。
|
|
2231
2297
|
// Grok/Composer は busy 表示文字列の実機根拠が未採取のため対象外(誤ブロックで起動不能にしない)。
|
|
2232
|
-
|
|
2233
|
-
|
|
2298
|
+
function isAgentTuiBusy(kind, screen) {
|
|
2299
|
+
if (kind === "cursor")
|
|
2300
|
+
return /ctrl\+c to stop/i.test(screen);
|
|
2301
|
+
if (kind === "codex" || kind === "claude")
|
|
2302
|
+
return /esc to interrupt/i.test(screen);
|
|
2303
|
+
return false;
|
|
2304
|
+
}
|
|
2234
2305
|
// ready gate 用: 入力欄マーカーがあっても busy 表示中は ready と数えない。
|
|
2235
2306
|
// frontend 推定(inferAgentFrontend)は「agent TUI が前面か」を見るだけなので isAgentTuiReady のまま。
|
|
2236
2307
|
function isAgentTuiIdleReady(kind, screen) {
|
|
2237
2308
|
if (!isAgentTuiReady(kind, screen))
|
|
2238
2309
|
return false;
|
|
2239
|
-
return !(
|
|
2310
|
+
return !isAgentTuiBusy(kind, screen);
|
|
2240
2311
|
}
|
|
2241
2312
|
async function waitAgentTuiReadyImpl(kind, sample, sleepFn, opts = {}) {
|
|
2242
2313
|
const timeoutMs = opts.timeoutMs ?? AGENT_TUI_READY_TIMEOUT_MS;
|
|
@@ -2285,11 +2356,21 @@ function agentSubmitResidueTail(text) {
|
|
|
2285
2356
|
return cps.slice(-AGENT_SUBMIT_RESIDUE_TAIL_CHARS).join("");
|
|
2286
2357
|
}
|
|
2287
2358
|
function agentSubmitResidueOnScreen(kind, screen, tail) {
|
|
2359
|
+
// Cursorは送信成立後のactive turnで明示的な停止UIを出す。scrollbackに残った旧「>」を
|
|
2360
|
+
// composerと誤認すると、実際には走っているpromptをsubmit座礁として報告してしまう。
|
|
2361
|
+
if (kind === "cursor" && isAgentTuiBusy(kind, screen))
|
|
2362
|
+
return false;
|
|
2288
2363
|
const lines = screen.split("\n");
|
|
2289
2364
|
// 入力欄マーカーは ready 判定と同じ記号を行頭基準で探す。submit 済みの transcript echo は
|
|
2290
2365
|
// マーカー行より上に出るため、最後のマーカー行以降だけを composer 領域として見る。
|
|
2291
2366
|
// grok/composer は Windows native 描画(`>`・実測 1.0.4)も ready 判定と同様に受ける。
|
|
2292
|
-
const markerRe = kind === "codex"
|
|
2367
|
+
const markerRe = kind === "codex"
|
|
2368
|
+
? CODEX_COMPOSER_MARKER_RE
|
|
2369
|
+
: kind === "claude"
|
|
2370
|
+
? CLAUDE_COMPOSER_MARKER_RE
|
|
2371
|
+
: kind === "cursor"
|
|
2372
|
+
? CURSOR_COMPOSER_MARKER_RE
|
|
2373
|
+
: GROK_COMPOSER_MARKER_RE;
|
|
2293
2374
|
let markerIdx = -1;
|
|
2294
2375
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2295
2376
|
if (markerRe.test(lines[i])) {
|
|
@@ -2428,7 +2509,7 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
2428
2509
|
const ready = await waitAgentTuiReady(name, meta, o.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
|
|
2429
2510
|
if (!ready.ready) {
|
|
2430
2511
|
return {
|
|
2431
|
-
text: `initial_prompt=not_sent vendor=${meta.kind} ready=false samples=${ready.samples}\n` +
|
|
2512
|
+
text: `initial_prompt=not_sent vendor=${meta.kind} ready=false samples=${ready.samples} harness=${agentHarness(meta.kind)}\n` +
|
|
2432
2513
|
`agent session '${name}' の ${agentLabel(meta.kind)} TUI が入力受付状態になりません。prompt は送信していません。`,
|
|
2433
2514
|
event_cursor: null,
|
|
2434
2515
|
submit_residue: null,
|
|
@@ -2449,7 +2530,7 @@ export async function sendInitialAgentPrompt(name, text, o = {}) {
|
|
|
2449
2530
|
}
|
|
2450
2531
|
const residue = await detectAgentSubmitResidue(name, meta.kind, text);
|
|
2451
2532
|
return {
|
|
2452
|
-
text: `initial_prompt=pending vendor=${meta.kind} event_cursor=${startOffset}\n` +
|
|
2533
|
+
text: `initial_prompt=pending vendor=${meta.kind} event_cursor=${startOffset} harness=${agentHarness(meta.kind)}\n` +
|
|
2453
2534
|
`起動時 prompt を送信した。${agentDispatchGuide(name, startOffset)}` +
|
|
2454
2535
|
agentSubmitResidueWarning(name, residue.residue),
|
|
2455
2536
|
event_cursor: startOffset,
|
|
@@ -2542,6 +2623,48 @@ export async function configureAgent(name, opts) {
|
|
|
2542
2623
|
schema: "aiterm.agent-configure-result.v1",
|
|
2543
2624
|
session_id: name,
|
|
2544
2625
|
provider: "claude",
|
|
2626
|
+
harness: agentHarness("claude"),
|
|
2627
|
+
model,
|
|
2628
|
+
reasoning_effort: effort,
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
if (meta.kind === "cursor") {
|
|
2632
|
+
validateCursorModelEffort(model, effort);
|
|
2633
|
+
if (!model)
|
|
2634
|
+
throw new AitermError("Cursorのreasoning_effort変更にはmodelも指定してください", 2);
|
|
2635
|
+
const bin = resolveAgentBin("cursor");
|
|
2636
|
+
if (!bin)
|
|
2637
|
+
throw new AitermError("Cursor Agent CLI の CLI が見つかりません", 2);
|
|
2638
|
+
assertCursorModelAvailable(bin, meta.cwd ?? process.cwd(), model, effort);
|
|
2639
|
+
// Cursor TUIの `/model <base model>` はmodelを直接切り替える一方、flatten済みcatalog ID
|
|
2640
|
+
// (例: gpt-5.6-luna-high)を渡すとno matchesのfilter画面へ入る。effortは標準model pickerの
|
|
2641
|
+
// parameter editorで選び、同じsessionと会話contextを保つ。
|
|
2642
|
+
await sendAgentPromptText(name, `/model ${model}`);
|
|
2643
|
+
const modelReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
2644
|
+
if (!modelReady.ready)
|
|
2645
|
+
throw new AitermError("Cursorのmodel変更完了を確認できません", 2);
|
|
2646
|
+
if (effort) {
|
|
2647
|
+
await sendAgentPromptText(name, "/model");
|
|
2648
|
+
await waitForScreenText(name, "Available models");
|
|
2649
|
+
sendMenuChoice(name, "Tab");
|
|
2650
|
+
const parameterScreen = await waitForScreenText(name, "Edit Parameters");
|
|
2651
|
+
const navigation = cursorEffortNavigation(parameterScreen, effort);
|
|
2652
|
+
for (let i = 0; i < navigation.down; i++)
|
|
2653
|
+
sendMenuChoice(name, "Down");
|
|
2654
|
+
sendMenuChoice(name, "Enter");
|
|
2655
|
+
await waitForScreenText(name, `${navigation.label} ✓`);
|
|
2656
|
+
sendMenuChoice(name, "Escape");
|
|
2657
|
+
await waitForScreenText(name, "Available models");
|
|
2658
|
+
sendMenuChoice(name, "Enter");
|
|
2659
|
+
const effortReady = await waitAgentTuiReady(name, meta, AGENT_TUI_READY_TIMEOUT_MS);
|
|
2660
|
+
if (!effortReady.ready)
|
|
2661
|
+
throw new AitermError("Cursorのreasoning effort変更完了を確認できません", 2);
|
|
2662
|
+
}
|
|
2663
|
+
return {
|
|
2664
|
+
schema: "aiterm.agent-configure-result.v1",
|
|
2665
|
+
session_id: name,
|
|
2666
|
+
provider: "cursor",
|
|
2667
|
+
harness: agentHarness("cursor"),
|
|
2545
2668
|
model,
|
|
2546
2669
|
reasoning_effort: effort,
|
|
2547
2670
|
};
|
|
@@ -2563,6 +2686,7 @@ export async function configureAgent(name, opts) {
|
|
|
2563
2686
|
schema: "aiterm.agent-configure-result.v1",
|
|
2564
2687
|
session_id: name,
|
|
2565
2688
|
provider: meta.kind,
|
|
2689
|
+
harness: agentHarness(meta.kind),
|
|
2566
2690
|
model,
|
|
2567
2691
|
reasoning_effort: effort,
|
|
2568
2692
|
};
|
|
@@ -2601,6 +2725,7 @@ export async function configureAgent(name, opts) {
|
|
|
2601
2725
|
schema: "aiterm.agent-configure-result.v1",
|
|
2602
2726
|
session_id: name,
|
|
2603
2727
|
provider: "codex",
|
|
2728
|
+
harness: agentHarness("codex"),
|
|
2604
2729
|
model,
|
|
2605
2730
|
reasoning_effort: effort,
|
|
2606
2731
|
};
|
|
@@ -2635,6 +2760,11 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
2635
2760
|
}
|
|
2636
2761
|
}
|
|
2637
2762
|
const startOffset = agentCompletionCursor(meta);
|
|
2763
|
+
// promptなしで起動したCursorは、最初のdispatch時点ではtranscriptとの相関markerをまだ持たない。
|
|
2764
|
+
// その1回だけlaunch contextを加え、以後はbind済みconversationへ通常textだけを送る。
|
|
2765
|
+
const dispatchText = meta.kind === "cursor" && !meta.vendor_session_id
|
|
2766
|
+
? `${subagentInstruction(meta)}\n\n${text}`
|
|
2767
|
+
: text;
|
|
2638
2768
|
if (meta.kind === "claude") {
|
|
2639
2769
|
// durable/anonymousを分岐する前に同じsend preflightを通す。拒否されるpromptの
|
|
2640
2770
|
// receipt/active markerだけを残して、来ないStopを待つ状態を作らない。
|
|
@@ -2645,7 +2775,7 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
2645
2775
|
else
|
|
2646
2776
|
reserveAnonymousClaudeTurn(meta);
|
|
2647
2777
|
}
|
|
2648
|
-
send(name,
|
|
2778
|
+
send(name, dispatchText, {
|
|
2649
2779
|
enter: false,
|
|
2650
2780
|
force: o.force,
|
|
2651
2781
|
raw: o.raw,
|
|
@@ -2657,12 +2787,13 @@ export async function dispatchAgentTurn(name, text, o = {}) {
|
|
|
2657
2787
|
// Codex TUI は literal text 投入直後の Enter を取り落とすことがある。agent 経路だけ submit を分離する。
|
|
2658
2788
|
await sleep(AGENT_SUBMIT_DELAY_MS);
|
|
2659
2789
|
sendKey(name, "Enter", { preserveAgentOperation: meta.kind === "claude" });
|
|
2660
|
-
const residue = await detectAgentSubmitResidue(name, meta.kind,
|
|
2790
|
+
const residue = await detectAgentSubmitResidue(name, meta.kind, dispatchText);
|
|
2661
2791
|
return {
|
|
2662
2792
|
schema: "aiterm.agent-dispatch.v1",
|
|
2663
2793
|
session_id: meta.aiterm_session,
|
|
2664
2794
|
launch_id: meta.launch_id,
|
|
2665
2795
|
vendor: meta.kind,
|
|
2796
|
+
harness: agentHarness(meta.kind),
|
|
2666
2797
|
event_cursor: startOffset,
|
|
2667
2798
|
operation_id: operationId,
|
|
2668
2799
|
submit_residue: residue.residue,
|
|
@@ -2776,6 +2907,8 @@ function buildAgentCmd(kind, bin, model, effort, prompt, meta = null) {
|
|
|
2776
2907
|
return buildClaudeAgentCmd(bin, model, effort, prompt, meta);
|
|
2777
2908
|
if (kind === "codex")
|
|
2778
2909
|
return buildCodexAgentCmd(bin, model, effort, prompt, meta);
|
|
2910
|
+
if (kind === "cursor")
|
|
2911
|
+
return buildCursorAgentCmd(bin, model, effort, prompt, meta);
|
|
2779
2912
|
return buildGrokAgentCmd(kind, bin, model, effort, prompt, meta);
|
|
2780
2913
|
}
|
|
2781
2914
|
function agentEnvPrefix(meta, sid, envVars = []) {
|
|
@@ -2798,7 +2931,7 @@ function agentEnvPrefix(meta, sid, envVars = []) {
|
|
|
2798
2931
|
`AITERM_AGENT_LINEAGE=${shq(meta.lineage ?? `host-root>${meta.kind}:${sid}`)}`,
|
|
2799
2932
|
`AITERM_AGENT_DELEGATION_ALLOWED=${shq(meta.delegation_allowed === true ? "true" : "false")}`,
|
|
2800
2933
|
];
|
|
2801
|
-
if (meta.kind === "claude" || meta.kind === "codex")
|
|
2934
|
+
if (meta.kind === "claude" || meta.kind === "codex" || meta.kind === "cursor")
|
|
2802
2935
|
return common;
|
|
2803
2936
|
return [...grokEnvTokens(meta), ...common];
|
|
2804
2937
|
})();
|
|
@@ -2807,6 +2940,8 @@ function agentEnvPrefix(meta, sid, envVars = []) {
|
|
|
2807
2940
|
function buildAgentLaunchNote(kind, model, effort, meta) {
|
|
2808
2941
|
if (kind === "claude")
|
|
2809
2942
|
return claudeLaunchNote(model, effort, meta);
|
|
2943
|
+
if (kind === "cursor")
|
|
2944
|
+
return cursorLaunchNote(model, effort, meta);
|
|
2810
2945
|
if (kind !== "codex")
|
|
2811
2946
|
return grokLaunchNote(kind, model, effort, meta);
|
|
2812
2947
|
return codexLaunchNote(model, effort, meta);
|
|
@@ -2861,6 +2996,8 @@ export function openAgent(kind, opts = {}) {
|
|
|
2861
2996
|
if (effort && kind === "claude" && !CLAUDE_EFFORTS.has(effort)) {
|
|
2862
2997
|
throw new AitermError("Claude Code の reasoning_effort は low/medium/high/xhigh/max のいずれかです", 2);
|
|
2863
2998
|
}
|
|
2999
|
+
if (kind === "cursor")
|
|
3000
|
+
validateCursorModelEffort(model, effort);
|
|
2864
3001
|
const agentDone = !!opts.agent_done;
|
|
2865
3002
|
const launchOperationId = opts.launch_operation_id == null
|
|
2866
3003
|
? null
|
|
@@ -2890,7 +3027,13 @@ export function openAgent(kind, opts = {}) {
|
|
|
2890
3027
|
ownTelemetryFailure("AITERM.VENDOR_LAUNCHER_FAILED", error, 2);
|
|
2891
3028
|
}
|
|
2892
3029
|
if (!bin) {
|
|
2893
|
-
const where = kind === "claude"
|
|
3030
|
+
const where = kind === "claude"
|
|
3031
|
+
? "~/.local/bin/claude"
|
|
3032
|
+
: kind === "codex"
|
|
3033
|
+
? "~/.local/bin/codex"
|
|
3034
|
+
: kind === "cursor"
|
|
3035
|
+
? "~/.local/bin/cursor-agent"
|
|
3036
|
+
: "~/.grok/bin/grok";
|
|
2894
3037
|
ownTelemetryFailure("AITERM.VENDOR_LAUNCHER_FAILED", new AitermError(`${label} の CLI が見つかりません(${where} か PATH が必要)`, 2), 2);
|
|
2895
3038
|
}
|
|
2896
3039
|
// cwd 検証(session を作る前に。cd 失敗はシェル内で静かに死に「起動した」と偽成功を返すため)。
|
|
@@ -2930,6 +3073,8 @@ export function openAgent(kind, opts = {}) {
|
|
|
2930
3073
|
}
|
|
2931
3074
|
if (kind === "claude")
|
|
2932
3075
|
assertClaudeAuthenticationReady(bin);
|
|
3076
|
+
if (kind === "cursor")
|
|
3077
|
+
assertCursorAuthenticationReady(bin);
|
|
2933
3078
|
// Windows の起動コマンドは native psmux pane の Git Bash で走る(WSL 橋・/mnt/c 変換は
|
|
2934
3079
|
// e3f5fc8 で全廃。bin/cwd は Windows パスの forward slash 形のまま渡す)。
|
|
2935
3080
|
// 未検証リスク: npm グローバル導入の codex.cmd/.bat シムの対話 TUI 描画は実 Windows でしか確認
|
|
@@ -2951,6 +3096,9 @@ export function openAgent(kind, opts = {}) {
|
|
|
2951
3096
|
if (requestedModel)
|
|
2952
3097
|
assertGrokModelAvailable(bin, cwd ?? process.cwd(), requestedModel);
|
|
2953
3098
|
}
|
|
3099
|
+
if (kind === "cursor" && model) {
|
|
3100
|
+
assertCursorModelAvailable(bin, cwd ?? process.cwd(), model, effort);
|
|
3101
|
+
}
|
|
2954
3102
|
let sid;
|
|
2955
3103
|
let hint;
|
|
2956
3104
|
try {
|
|
@@ -2971,7 +3119,9 @@ export function openAgent(kind, opts = {}) {
|
|
|
2971
3119
|
? createClaudeAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", launchOperationId, launchRequestDigest, lineageContext, model, effort)
|
|
2972
3120
|
: kind === "codex"
|
|
2973
3121
|
? createCodexAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", { model, effort }, writeScope, lineageContext)
|
|
2974
|
-
:
|
|
3122
|
+
: kind === "cursor"
|
|
3123
|
+
? createCursorAgentMetadata(sid, cwd, opts.prompt ? "pending" : "none", writeScope, lineageContext)
|
|
3124
|
+
: createGrokAgentMetadata(kind, sid, cwd, opts.prompt ? "pending" : "none", grokAuthPath, writeScope, lineageContext)
|
|
2975
3125
|
: null;
|
|
2976
3126
|
if (meta)
|
|
2977
3127
|
agentMetadataNegativeCache.delete(sid);
|
|
@@ -3002,7 +3152,7 @@ export function openAgent(kind, opts = {}) {
|
|
|
3002
3152
|
}
|
|
3003
3153
|
throw failure;
|
|
3004
3154
|
}
|
|
3005
|
-
const driveHint = agentDone
|
|
3155
|
+
const driveHint = agentDone
|
|
3006
3156
|
? `TUI の描画には数秒かかる。少し置いてから pty_read(${sid}, screen:true) で画面を読み、` +
|
|
3007
3157
|
`turnはpty_send(${sid}, "...")で送る(自動で非ブロックdispatch=投げっぱなしでよい・完了通知はaiterm-wait)。中断はpty_key(${sid}, "C-c")、` +
|
|
3008
3158
|
`Stopが来ない場合の解除はpty_close(${sid})を使う。`
|
|
@@ -3036,7 +3186,7 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
3036
3186
|
}
|
|
3037
3187
|
// v0.16.0: launcher は常に managed(Stop hook つき)で立つ。手動運転したい場合は
|
|
3038
3188
|
// pty_open で素の PTY を開き、vendor CLI を自分で send する。
|
|
3039
|
-
// 第3要素は「起動時点でturnが走っているか」の event_cursor: Grok/Composer の argv prompt は
|
|
3189
|
+
// 第3要素は「起動時点でturnが走っているか」の event_cursor: Grok/Composer/Cursor の argv prompt は
|
|
3040
3190
|
// event file 新規作成直後の起動=境界0、prompt なしの起動は turn なし=null。
|
|
3041
3191
|
if (!prompt || (kind !== "codex" && kind !== "claude")) {
|
|
3042
3192
|
const [sid, hint] = openAgent(kind, {
|
|
@@ -3050,7 +3200,7 @@ export async function openAgentWithInitialPrompt(kind, opts = {}) {
|
|
|
3050
3200
|
write_scope: opts.write_scope,
|
|
3051
3201
|
env_vars: opts.env_vars,
|
|
3052
3202
|
});
|
|
3053
|
-
// argv prompt(grok/composer)は composer を経由しないため submit 座礁観測の対象外。
|
|
3203
|
+
// argv prompt(grok/composer/cursor)は composer を経由しないため submit 座礁観測の対象外。
|
|
3054
3204
|
return [sid, hint, prompt ? 0 : null, null];
|
|
3055
3205
|
}
|
|
3056
3206
|
const [sid, hint] = openAgent(kind, {
|