aiterm-mcp 0.58.4 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -1
- package/README.ja.md +15 -5
- package/README.md +14 -4
- package/dist/agent-shared.js +12 -1
- package/dist/core.js +868 -106
- package/dist/cursor-parent-receive.js +2 -1
- package/dist/fast-mode.js +22 -0
- package/dist/harnesses/claude.js +137 -19
- package/dist/harnesses/codex.js +95 -3
- package/dist/harnesses/cursor.js +96 -12
- package/dist/harnesses/grok.js +159 -2
- package/dist/index.js +41 -7
- package/dist/live-node.js +17 -0
- package/dist/model-catalog.js +1 -0
- package/dist/parent-delivery-cli.js +3 -2
- package/dist/runtime-error-report.js +2 -1
- package/dist/runtime-error-store.js +3 -2
- package/dist/user-config.js +138 -0
- package/docs/DESIGN.md +11 -0
- package/docs/RELEASE.md +5 -0
- package/package.json +2 -2
package/dist/harnesses/grok.js
CHANGED
|
@@ -117,16 +117,41 @@ export function grokCatalogFromInitialize(stdout, summary = "") {
|
|
|
117
117
|
// 候補に無い既定値は黙って捨てず、checkedCatalogで形式異常にする。
|
|
118
118
|
default_effort: efforts.length > 0 ? defaultEffort : null,
|
|
119
119
|
hidden: false,
|
|
120
|
+
fast: false,
|
|
120
121
|
};
|
|
121
122
|
});
|
|
123
|
+
// Grokのfastは別のmodel(表示名が「<元の表示名> Fast」。1.0.50: Grok 4.7 と Grok 4.7 Fast=grok-4.7-build-fast)。
|
|
124
|
+
// fast版は別のmodelとして並べず、元のmodelの fast: true にまとめる。元のmodelが一覧に無いfast版は、そのまま1つのmodelとして残す。
|
|
125
|
+
const fastIds = grokFastVariants(models);
|
|
126
|
+
const variants = new Set(Object.values(fastIds));
|
|
127
|
+
const current = typeof state.currentModelId === "string" ? state.currentModelId : null;
|
|
128
|
+
const baseOf = (id) => Object.keys(fastIds).find((base) => fastIds[base] === id) ?? id;
|
|
122
129
|
return checkedCatalog("Grok", {
|
|
123
130
|
source: "grok agent stdio initialize (_meta.modelState)",
|
|
124
131
|
harness_version: typeof meta.agentVersion === "string" ? meta.agentVersion : null,
|
|
125
|
-
default_model:
|
|
132
|
+
default_model: baseOf(current),
|
|
126
133
|
adapter_efforts: {},
|
|
127
|
-
models
|
|
134
|
+
models: models.filter((model) => !variants.has(model.id))
|
|
135
|
+
.map((model) => ({ ...model, fast: model.id in fastIds })),
|
|
136
|
+
fast_ids: fastIds,
|
|
128
137
|
});
|
|
129
138
|
}
|
|
139
|
+
/** 元のmodel ID → fast版のmodel ID。表示名が「<元の表示名> Fast」のmodelを、その元のmodelのfast版と読む。 */
|
|
140
|
+
export function grokFastVariants(models) {
|
|
141
|
+
const byName = new Map(models.flatMap((model) => model.display_name ? [[model.display_name, model.id]] : []));
|
|
142
|
+
const pairs = {};
|
|
143
|
+
for (const model of models) {
|
|
144
|
+
const match = model.display_name?.match(/^(.+) Fast$/);
|
|
145
|
+
const base = match ? byName.get(match[1]) : undefined;
|
|
146
|
+
if (base && base !== model.id)
|
|
147
|
+
pairs[base] = model.id;
|
|
148
|
+
}
|
|
149
|
+
return pairs;
|
|
150
|
+
}
|
|
151
|
+
/** model IDが、一覧の上でほかのmodelのfast版なら、その元のmodel ID。 */
|
|
152
|
+
export function grokFastBase(catalog, model) {
|
|
153
|
+
return Object.keys(catalog.fast_ids ?? {}).find((base) => catalog.fast_ids?.[base] === model) ?? null;
|
|
154
|
+
}
|
|
130
155
|
export function assertGrokModelAvailable(bin, cwd, model) {
|
|
131
156
|
const models = grokModelCatalog(bin, cwd);
|
|
132
157
|
if (!models.includes(model)) {
|
|
@@ -630,3 +655,135 @@ export function grokAuthPane(screen) {
|
|
|
630
655
|
}
|
|
631
656
|
return { url, user_code: embedded, input_required: false, message: null };
|
|
632
657
|
}
|
|
658
|
+
// ---- ファスト。Grokのfastは別のmodel。Grokは、起動の --model と、動いている席の `/model <ID> [effort]`・`/effort` で、
|
|
659
|
+
// 利用者のconfig.tomlの [models] の default と default_reasoning_effort を書き換える(1.0.50で確かめた。「✓ Default model: …」)。
|
|
660
|
+
// 席だけの切り替えにするため、切り替えの直前の値へ戻す。
|
|
661
|
+
const GROK_DEFAULT_KEYS = ["default", "default_reasoning_effort"];
|
|
662
|
+
/** [models] の節の中の `<key> = …` の行の番号。節や行が無ければnull。 */
|
|
663
|
+
function grokModelsLine(rows, key) {
|
|
664
|
+
const start = rows.findIndex((row) => /^\s*\[models\]\s*(?:#.*)?$/.test(row));
|
|
665
|
+
if (start < 0)
|
|
666
|
+
return null;
|
|
667
|
+
for (let index = start + 1; index < rows.length && !/^\s*\[/.test(rows[index]); index++) {
|
|
668
|
+
if (new RegExp(`^\\s*${key}\\s*=`).test(rows[index]))
|
|
669
|
+
return index;
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
export function readGrokModelDefaults(configPath) {
|
|
674
|
+
let body;
|
|
675
|
+
try {
|
|
676
|
+
body = fs.readFileSync(configPath, "utf8");
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
body = null;
|
|
680
|
+
}
|
|
681
|
+
return grokModelDefaultsFromBody(body);
|
|
682
|
+
}
|
|
683
|
+
/** config.tomlの中身(読めなかった時はnull)から、[models] の既定を読む。 */
|
|
684
|
+
export function grokModelDefaultsFromBody(body) {
|
|
685
|
+
if (body === null)
|
|
686
|
+
return { body: null, default: { present: false, value: null }, default_reasoning_effort: { present: false, value: null } };
|
|
687
|
+
const rows = body.split(/\r?\n/);
|
|
688
|
+
const pick = (key) => {
|
|
689
|
+
const line = grokModelsLine(rows, key);
|
|
690
|
+
if (line === null)
|
|
691
|
+
return { present: false, value: null };
|
|
692
|
+
const value = rows[line].match(/=\s*"([^"\\]*)"\s*(?:#.*)?$/);
|
|
693
|
+
return { present: true, value: value ? value[1] : null };
|
|
694
|
+
};
|
|
695
|
+
return { body, default: pick("default"), default_reasoning_effort: pick("default_reasoning_effort") };
|
|
696
|
+
}
|
|
697
|
+
function withoutGrokDefaults(body) {
|
|
698
|
+
const rows = body.split(/\r?\n/);
|
|
699
|
+
for (const key of GROK_DEFAULT_KEYS) {
|
|
700
|
+
const line = grokModelsLine(rows, key);
|
|
701
|
+
if (line !== null)
|
|
702
|
+
rows.splice(line, 1);
|
|
703
|
+
}
|
|
704
|
+
return rows.join("\n");
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Grokがconfig.tomlへ書いた [models] の既定(default・default_reasoning_effort)を、切り替えの直前の値へ戻す。
|
|
708
|
+
* 切り替えの前後で、ほかの行が同じなら前の中身をそのまま書き戻す。ほかの行も変わっていたら、既定の行だけを直す。
|
|
709
|
+
* 前の中身を読めなかった時、元の値が読めない書き方だった時、今のfileに [models] の節が無い時は、その項目に触らない。
|
|
710
|
+
*/
|
|
711
|
+
export function restoreGrokModelDefaults(configPath, before) {
|
|
712
|
+
if (before.body === null)
|
|
713
|
+
return;
|
|
714
|
+
let now;
|
|
715
|
+
try {
|
|
716
|
+
now = fs.readFileSync(configPath, "utf8");
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (now === before.body)
|
|
722
|
+
return;
|
|
723
|
+
const eol = now.includes("\r\n") ? "\r\n" : "\n";
|
|
724
|
+
const rows = now.split(/\r?\n/);
|
|
725
|
+
for (const key of GROK_DEFAULT_KEYS) {
|
|
726
|
+
const pin = before[key];
|
|
727
|
+
if (pin.present && pin.value === null)
|
|
728
|
+
continue;
|
|
729
|
+
const line = grokModelsLine(rows, key);
|
|
730
|
+
if (pin.present) {
|
|
731
|
+
const wanted = `${key} = ${JSON.stringify(pin.value)}`;
|
|
732
|
+
if (line !== null)
|
|
733
|
+
rows[line] = wanted;
|
|
734
|
+
else {
|
|
735
|
+
const header = rows.findIndex((row) => /^\s*\[models\]\s*(?:#.*)?$/.test(row));
|
|
736
|
+
if (header >= 0)
|
|
737
|
+
rows.splice(header + 1, 0, wanted);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
else if (line !== null) {
|
|
741
|
+
rows.splice(line, 1);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const edited = rows.join(eol);
|
|
745
|
+
if (edited === now)
|
|
746
|
+
return;
|
|
747
|
+
// ほかの行が前と同じなら、前の中身(空白や並びもそのまま)を書き戻す。
|
|
748
|
+
const next = withoutGrokDefaults(edited) === withoutGrokDefaults(before.body) ? before.body : edited;
|
|
749
|
+
const temp = `${configPath}.aiterm-restore-${process.pid}`;
|
|
750
|
+
fs.writeFileSync(temp, next, { mode: fs.statSync(configPath).mode & 0o777 });
|
|
751
|
+
try {
|
|
752
|
+
fs.renameSync(temp, configPath);
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
// Windowsでは、ほかのprocessがfileを開いている間、置き換えに失敗する事がある。途中のfileを残さず、呼び手に次の回で試させる。
|
|
756
|
+
try {
|
|
757
|
+
fs.unlinkSync(temp);
|
|
758
|
+
}
|
|
759
|
+
catch { /* 残っても中身は前の値 */ }
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* 足元の枠の行の「<modelの表示> (<effort>)」から、modelの表示(「Grok 4.7 Fast」)を読む。権限の表示(· always-approve)は、付く時と付かない時がある。
|
|
765
|
+
* 切り替えの知らせ(Switched to Grok 4.7 Fast (high effort))は、かっこの中に空白があるので拾わない。
|
|
766
|
+
*/
|
|
767
|
+
export function grokFooterModelName(screen) {
|
|
768
|
+
let name = null;
|
|
769
|
+
for (const line of screen.split("\n")) {
|
|
770
|
+
const match = line.match(/(Grok [^()·│╯╰─]+?) \([a-z-]+\)/);
|
|
771
|
+
if (match)
|
|
772
|
+
name = match[1].trim();
|
|
773
|
+
}
|
|
774
|
+
return name;
|
|
775
|
+
}
|
|
776
|
+
/** 足元の「Grok 4.7 Fast (high)」から、今のmodelを一覧のIDで返す。base は元のmodel、fast はfast版かどうか。 */
|
|
777
|
+
export function grokModelFromFooter(screen, catalog) {
|
|
778
|
+
const name = grokFooterModelName(screen);
|
|
779
|
+
if (!name)
|
|
780
|
+
return null;
|
|
781
|
+
const fastIds = catalog.fast_ids ?? {};
|
|
782
|
+
for (const model of catalog.models) {
|
|
783
|
+
if (model.display_name === name)
|
|
784
|
+
return { id: model.id, base: model.id, fast: false };
|
|
785
|
+
if (fastIds[model.id] && `${model.display_name} Fast` === name)
|
|
786
|
+
return { id: fastIds[model.id], base: model.id, fast: true };
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -834,15 +834,27 @@ registerRemoteAwareTool("agent_auth", {
|
|
|
834
834
|
return fail(error);
|
|
835
835
|
}
|
|
836
836
|
});
|
|
837
|
+
// ファスト(高速の応答。ADR 0114)。断りの符号は4つのharnessで共通で、文の先頭に付く。
|
|
838
|
+
const fastModeErrorCodes = ["FAST_MODE_NOT_SUPPORTED", "FAST_MODE_UNAVAILABLE", "FAST_MODE_MODEL_CONFLICT", "FAST_MODE_UNCONFIRMED"];
|
|
839
|
+
const fastModeFailureSchema = z.object({
|
|
840
|
+
code: z.enum(fastModeErrorCodes),
|
|
841
|
+
message: z.string(),
|
|
842
|
+
});
|
|
843
|
+
const fastModeDesc = "ファスト(harnessの高速の応答。費用や利用量が増える)。席は必ず通常の速度で起きる。trueはその席をfastへ切り替え、falseは通常の速度にする" +
|
|
844
|
+
"(fastに対応していないmodelでもfalseは受ける)。切り替えは席だけに留め、harnessが利用者の既定へ書いた分は切り替えの直前の値へ戻す。" +
|
|
845
|
+
"断りは文の先頭の符号で分かる: FAST_MODE_NOT_SUPPORTED(そのmodelは対応していない)、FAST_MODE_UNAVAILABLE(アカウントや契約で使えない)、" +
|
|
846
|
+
"FAST_MODE_MODEL_CONFLICT(modelへfast版のIDを書いてfalseを渡した)、FAST_MODE_UNCONFIRMED(切り替えた後の状態を確かめられない)。";
|
|
837
847
|
registerRemoteAwareTool("agent_configure", {
|
|
838
|
-
description: "起動済みのClaude/Codex/Grok/Cursor agent sessionを再起動せず、会話contextを保ったままmodel/reasoning effortを変更する。" +
|
|
848
|
+
description: "起動済みのClaude/Codex/Grok/Cursor agent sessionを再起動せず、会話contextを保ったままmodel/reasoning effort/fastを変更する。" +
|
|
839
849
|
"各harnessのCLI標準model操作を使う。Cursorのreasoning_effort変更はmodelと同時指定する。" +
|
|
840
850
|
"Claude Codeの席では /model の選ぶ画面の「この会話だけ」(s)で替え、端末の ~/.claude/settings.json の既定は書き換えない。" +
|
|
841
|
-
"その選び方が無い版、選ぶ画面に無いmodel、そのmodelで選べないeffortは、何も変えずに断る。"
|
|
851
|
+
"その選び方が無い版、選ぶ画面に無いmodel、そのmodelで選べないeffortは、何も変えずに断る。" +
|
|
852
|
+
"fastだけの指定も受ける。" + fastModeDesc,
|
|
842
853
|
inputSchema: {
|
|
843
854
|
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
844
855
|
model: z.string().min(1).nullish().describe("変更後のmodel。省略時はmodelを変更しない"),
|
|
845
856
|
reasoning_effort: z.string().min(1).nullish().describe("変更後のreasoning effort。省略時はeffortを変更しない"),
|
|
857
|
+
fast: z.boolean().nullish().describe("変更後のfast。省略時は、その席の今の状態を変えない"),
|
|
846
858
|
},
|
|
847
859
|
outputSchema: {
|
|
848
860
|
schema: z.literal("aiterm.agent-configure-result.v1"),
|
|
@@ -851,10 +863,11 @@ registerRemoteAwareTool("agent_configure", {
|
|
|
851
863
|
harness: z.enum(["claude-code", "codex-cli", "grok-cli", "cursor-cli"]),
|
|
852
864
|
model: z.string().nullable(),
|
|
853
865
|
reasoning_effort: z.string().nullable(),
|
|
866
|
+
fast: z.boolean().nullable().optional().describe("切り替えた後の、席のfastの状態。Aitermが確かめた事の無い席(前の版が起こした席)はnull"),
|
|
854
867
|
},
|
|
855
|
-
}, async ({ session_id, model, reasoning_effort }) => {
|
|
868
|
+
}, async ({ session_id, model, reasoning_effort, fast }) => {
|
|
856
869
|
try {
|
|
857
|
-
const result = await core.configureAgent(session_id, { model, reasoning_effort });
|
|
870
|
+
const result = await core.configureAgent(session_id, { model, reasoning_effort, fast });
|
|
858
871
|
return {
|
|
859
872
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
860
873
|
structuredContent: { ...result },
|
|
@@ -870,11 +883,14 @@ const agentModelChoiceSchema = z.object({
|
|
|
870
883
|
efforts: z.array(z.string()).describe("このmodelで選べるreasoning_effort。空ならeffort非対応"),
|
|
871
884
|
default_effort: z.string().nullable(),
|
|
872
885
|
hidden: z.boolean().describe("harnessが一覧で隠すmodel(Codexのinclude_hidden時だけtrueがある)"),
|
|
886
|
+
fast: z.boolean().describe("このharness・このmodelでfastへ切り替えられるか(agent_launch/agent_configureのfast: true)。" +
|
|
887
|
+
"fastが別のmodel IDのharness(Cursor・Grok)は、fast版を別のmodelとして並べず、元のmodelのここがtrueになる"),
|
|
873
888
|
});
|
|
874
889
|
registerRemoteAwareTool("agent_models", {
|
|
875
890
|
description: "harnessが今返すmodelとreasoning effortの候補を、そのharnessの公式の一覧から取得する。promptもturnも送らず推論を消費しない。" +
|
|
876
891
|
"Codexはapp-server model/list、Claude Codeはstream-json initializeのmodels、Grokはagent stdio initializeのmodelState、" +
|
|
877
892
|
"Cursorはcursor-agent modelsを素のmodel IDとeffortへ分けたもの。返るidとeffortsはagent_launch/agent_configureへそのまま渡せる。" +
|
|
893
|
+
"modelごとのfastは、そのmodelでfastへ切り替えられるか(harnessが返す値)。fast_unavailableは、アカウントの都合でどのmodelでも使えない時の訳。" +
|
|
878
894
|
"取得不能はMODEL_CATALOG_UNAVAILABLE、形式異常はMODEL_CATALOG_INVALIDのエラーで返し、別の一覧へfallbackしない。" +
|
|
879
895
|
"remoteを付けると別端末のharnessの候補を返す。",
|
|
880
896
|
inputSchema: {
|
|
@@ -890,6 +906,7 @@ registerRemoteAwareTool("agent_models", {
|
|
|
890
906
|
default_model: z.string().nullable().describe("model省略時にharnessが使うmodel。一覧のIDで表せなければnull"),
|
|
891
907
|
efforts: z.array(z.string()).describe("全modelのeffortの和"),
|
|
892
908
|
adapter_efforts: z.record(z.string(), z.string()).describe("harnessの一覧には無く、Aitermのadapterが足したeffortと理由"),
|
|
909
|
+
fast_unavailable: fastModeFailureSchema.nullable().optional().describe("このアカウント・この端末では、どのmodelでもfastを使えない時の訳(harnessが先に答えられる時だけ。今はClaude Code)。使える・分からない時はnull"),
|
|
893
910
|
models: z.array(agentModelChoiceSchema),
|
|
894
911
|
},
|
|
895
912
|
}, async ({ harness, cwd, include_hidden }) => {
|
|
@@ -907,6 +924,11 @@ registerRemoteAwareTool("agent_models", {
|
|
|
907
924
|
});
|
|
908
925
|
// 新規APIの選択軸はharness。旧4 launcher名は移行期間中の薄いaliasとして同じ実装へ流す。
|
|
909
926
|
const kindForHarness = (harness) => harness === "claude-code" ? "claude" : harness === "codex-cli" ? "codex" : harness === "cursor-cli" ? "cursor" : "grok";
|
|
927
|
+
const fastModeLaunchDesc = "ファスト(harnessの高速の応答。費用や利用量が増える)。省略とfalseは通常の速度で起こす(利用者の設定にfastが残っていても通常)。" +
|
|
928
|
+
"trueは通常で起こしてから、その席をfastへ切り替える。modelがfastに対応していない時(FAST_MODE_NOT_SUPPORTED)と、" +
|
|
929
|
+
"modelへfast版のIDを書いてfalseを渡した時(FAST_MODE_MODEL_CONFLICT)は起動を断る。アカウントの都合で使えない時・確かめられない時は、" +
|
|
930
|
+
"起動を断らずに通常の速度で起こし、返りの fast を false、fast_error に訳を入れる。" +
|
|
931
|
+
"modelへfast版のIDを直に書いて省略した時だけ、そのIDのまま起こす(返りの fast は true)。";
|
|
910
932
|
const agentModelDesc = (kind) => kind === "claude"
|
|
911
933
|
? "起動モデル(例: claude-sonnet-4-6)。省略時はClaude CLI既定"
|
|
912
934
|
: kind === "codex"
|
|
@@ -966,7 +988,7 @@ async function launchRemoteAgent(target, harness, args, extra) {
|
|
|
966
988
|
}
|
|
967
989
|
async function launchAgent(kind, args, extra) {
|
|
968
990
|
const supportsWriteScope = kind !== "claude";
|
|
969
|
-
const { prompt, image, throughline_source_session, throughline_supplement_file, model, reasoning_effort, env_vars, cwd, session_name, launch_operation_id, write_scope, trust_project } = args;
|
|
991
|
+
const { prompt, image, throughline_source_session, throughline_supplement_file, model, reasoning_effort, fast, env_vars, cwd, session_name, launch_operation_id, write_scope, trust_project } = args;
|
|
970
992
|
let delivery = null;
|
|
971
993
|
try {
|
|
972
994
|
if (!supportsWriteScope && write_scope !== undefined) {
|
|
@@ -975,12 +997,13 @@ async function launchAgent(kind, args, extra) {
|
|
|
975
997
|
const initialPrompt = image && image.length > 0 ? core.attachImages(prompt ?? "", image) : prompt ?? undefined;
|
|
976
998
|
if (initialPrompt || throughline_source_session)
|
|
977
999
|
delivery = await deliveryForRequest(extra);
|
|
978
|
-
const [sid, hint, eventCursor, submitResidue, initialDelivery, startup] = await core.openAgentWithInitialPrompt(kind, {
|
|
1000
|
+
const [sid, hint, eventCursor, submitResidue, initialDelivery, startup, fastState] = await core.openAgentWithInitialPrompt(kind, {
|
|
979
1001
|
prompt: initialPrompt,
|
|
980
1002
|
throughline_source_session,
|
|
981
1003
|
throughline_supplement_file,
|
|
982
1004
|
model: model ?? undefined,
|
|
983
1005
|
reasoning_effort: reasoning_effort ?? undefined,
|
|
1006
|
+
fast: fast ?? undefined,
|
|
984
1007
|
env_vars,
|
|
985
1008
|
trust_project,
|
|
986
1009
|
before_send: delivery?.before_send,
|
|
@@ -1001,6 +1024,8 @@ async function launchAgent(kind, args, extra) {
|
|
|
1001
1024
|
submit_residue: submitResidue,
|
|
1002
1025
|
initial_prompt: initialDelivery,
|
|
1003
1026
|
startup,
|
|
1027
|
+
fast: fastState.fast,
|
|
1028
|
+
fast_error: fastState.fast_error,
|
|
1004
1029
|
...(supportsWriteScope && write_scope !== undefined
|
|
1005
1030
|
? {
|
|
1006
1031
|
write_scope,
|
|
@@ -1012,8 +1037,11 @@ async function launchAgent(kind, args, extra) {
|
|
|
1012
1037
|
};
|
|
1013
1038
|
// 開始未確認(submitted_unconfirmed)は誤りにしない。sessionは立ち、promptは入力欄を離れ、配送も登録済み。
|
|
1014
1039
|
// 誤りで返すとClaude Code親は受け口のhookを走らせず、子の回答が届かない(ADR 0093)。状態はinitial_promptと本文で伝える。
|
|
1040
|
+
const fastLine = fastState.fast_error
|
|
1041
|
+
? `\nfast: false(通常の速度で起こした)。${fastState.fast_error.message}`
|
|
1042
|
+
: fast === true ? `\nfast: ${fastState.fast}` : "";
|
|
1015
1043
|
return {
|
|
1016
|
-
content: [{ type: "text", text: `session_id: ${sid}\n${hint}${deliveryIdLine(delivery)}` }],
|
|
1044
|
+
content: [{ type: "text", text: `session_id: ${sid}\n${hint}${fastLine}${deliveryIdLine(delivery)}` }],
|
|
1017
1045
|
structuredContent: structured,
|
|
1018
1046
|
};
|
|
1019
1047
|
}
|
|
@@ -1073,6 +1101,7 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
1073
1101
|
model: z.string().nullish().describe(agentModelDesc(kind)),
|
|
1074
1102
|
// CLI/model側の値集合が版で変わるため公開enumでは縛らない(core側も同方針)。
|
|
1075
1103
|
reasoning_effort: z.string().nullish().describe(agentEffortDesc(kind)),
|
|
1104
|
+
fast: z.boolean().nullish().describe(fastModeLaunchDesc),
|
|
1076
1105
|
env_vars: z.array(z.string()).optional().describe("起動したagentへ現在のMCP processから継承する環境変数名。値はtool引数へ渡さない"),
|
|
1077
1106
|
trust_project: z.boolean().optional().describe("対象projectを信頼し、既知のworkspace・project hooks・MCP初期同意を起動中に進める"),
|
|
1078
1107
|
cwd: z.string().nullish().describe("作業ディレクトリ(対象リポのルート等・任意)"),
|
|
@@ -1097,6 +1126,8 @@ function registerAgentTool(toolName, kind, desc) {
|
|
|
1097
1126
|
// 初回prompt dispatch後のsubmit座礁観測(additive)。true=composerに残存を確認(submit未成立の疑い)/
|
|
1098
1127
|
// false=残存を観測せず(submit成立の保証ではない)/ null=promptなし・判定不能。
|
|
1099
1128
|
submit_residue: z.boolean().nullable(),
|
|
1129
|
+
fast: z.boolean().optional().describe("起こした後の、席のfastの状態"),
|
|
1130
|
+
fast_error: fastModeFailureSchema.nullable().optional().describe("fastを頼まれたが、通常の速度で起こした時の訳"),
|
|
1100
1131
|
...writeScopeOutputSchema,
|
|
1101
1132
|
},
|
|
1102
1133
|
}, async (args, extra) => launchAgent(kind, args, extra));
|
|
@@ -1115,6 +1146,7 @@ server.registerTool("agent_launch", {
|
|
|
1115
1146
|
throughline_supplement_file: z.string().min(1).optional().describe("Throughline 0.10.8以降へそのまま渡すproject束縛済み長期記憶・知識の補足JSON path"),
|
|
1116
1147
|
model: z.string().nullish().describe("harnessが選ぶモデル。provider名ではなくlive catalog上のmodel ID"),
|
|
1117
1148
|
reasoning_effort: z.string().nullish().describe("harness adapterが標準CLI表現へ変換する思考レベル。Cursorではmodel同時指定が必要"),
|
|
1149
|
+
fast: z.boolean().nullish().describe(fastModeLaunchDesc),
|
|
1118
1150
|
env_vars: z.array(z.string()).optional().describe("現在のMCP processから継承する環境変数名"),
|
|
1119
1151
|
trust_project: z.boolean().optional().describe("対象projectを信頼し、既知のworkspace・project hooks・MCP初期同意を起動中に進める"),
|
|
1120
1152
|
cwd: z.string().nullish().describe("作業ディレクトリ(絶対パス・任意)"),
|
|
@@ -1138,6 +1170,8 @@ server.registerTool("agent_launch", {
|
|
|
1138
1170
|
wait_command: z.string().nullable(),
|
|
1139
1171
|
parent_delivery: parentDeliveryOutputSchema.optional(),
|
|
1140
1172
|
submit_residue: z.boolean().nullable(),
|
|
1173
|
+
fast: z.boolean().optional().describe("起こした後の、席のfastの状態(この項目を返さない版の別端末では無い)"),
|
|
1174
|
+
fast_error: fastModeFailureSchema.nullable().optional().describe("fastを頼まれたが、通常の速度で起こした時の訳"),
|
|
1141
1175
|
write_scope: z.string().optional(),
|
|
1142
1176
|
write_scope_enforcement: z.enum(["enforced_read_only", "declaration_only_unsupported"]).optional(),
|
|
1143
1177
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* このprocessのNodeを、後からでも起こせる場所で返す。自分でNodeを起こす時と、呼び手へ「起こす先」として渡す時に使う。
|
|
4
|
+
* `process.execPath`は起きた時の実体で、長く動くprocessでは後から消える事がある(Homebrewは版ごとの場所へ入れ、更新で前の版を消す。
|
|
5
|
+
* 2026-10-12、MacでNodeが26.10.0_1から26.11.1へ上がり、前の版から起きていたMCP process 19個が、無いNodeを起こそうとして黙って失敗していた)。
|
|
6
|
+
* Homebrewの時は同じformulaのopt(更新でも残る)を返す。実体が消えていて代わりも無い時は、PATHから探させる(`node`)。
|
|
7
|
+
* aiterm-steer-deliveryの同じ名前の関数と同じ決まり(0.4.6〜)。
|
|
8
|
+
*/
|
|
9
|
+
export function liveNodeExecutable(executable = process.execPath, exists = existsSync) {
|
|
10
|
+
const brew = /^(.*)\/Cellar\/(node(?:@\d+)?)\/[^/]+\/bin\/node$/.exec(executable);
|
|
11
|
+
if (brew) {
|
|
12
|
+
const opt = `${brew[1]}/opt/${brew[2]}/bin/node`;
|
|
13
|
+
if (exists(opt))
|
|
14
|
+
return opt;
|
|
15
|
+
}
|
|
16
|
+
return exists(executable) ? executable : "node";
|
|
17
|
+
}
|
package/dist/model-catalog.js
CHANGED
|
@@ -50,6 +50,7 @@ export function agentModelsResult(harness, catalog) {
|
|
|
50
50
|
default_model: catalog.default_model,
|
|
51
51
|
efforts: sortEfforts(catalog.models.flatMap(model => model.efforts)),
|
|
52
52
|
adapter_efforts: catalog.adapter_efforts,
|
|
53
|
+
fast_unavailable: catalog.fast_unavailable ?? null,
|
|
53
54
|
models: catalog.models,
|
|
54
55
|
};
|
|
55
56
|
}
|
|
@@ -22,6 +22,7 @@ import { createRequire } from "node:module";
|
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { realCodexHome, readCodexWakeResult } from "aiterm-steer-delivery";
|
|
24
24
|
import { AITERM_PROFILE } from "./steer-profile.js";
|
|
25
|
+
import { liveNodeExecutable } from "./live-node.js";
|
|
25
26
|
import { checkCodexParent, submitCodexParentAnswer, withCodexReceiver } from "./codex-parent-receiver.js";
|
|
26
27
|
import { codexHookDeliveryState, codexHookDirectory, codexInputDirectory } from "./codex-hook-state.js";
|
|
27
28
|
export const PARENT_DELIVERY_SCHEMA = "aiterm.parent-delivery.v1";
|
|
@@ -134,7 +135,7 @@ async function deliveryState(target, delivery) {
|
|
|
134
135
|
* アプリ配下のprocessは、PATHが細い事がある(2026-10-07、素のsshの環境からの呼び出しで起きた)。
|
|
135
136
|
* nodeの場所がPATHに無い時だけ頭へ足す。既にあれば、並びも中身も変えない。
|
|
136
137
|
*/
|
|
137
|
-
export function pathWithNode(env, node =
|
|
138
|
+
export function pathWithNode(env, node = liveNodeExecutable(), platform = process.platform) {
|
|
138
139
|
const windows = platform === "win32";
|
|
139
140
|
// Windowsの環境変数名は大文字小文字を区別しない(`Path`で入っている事が多い)。
|
|
140
141
|
const key = Object.keys(env).find(name => windows ? name.toLowerCase() === "path" : name === "PATH") ?? "PATH";
|
|
@@ -146,7 +147,7 @@ export function pathWithNode(env, node = process.execPath, platform = process.pl
|
|
|
146
147
|
}
|
|
147
148
|
/** この命令の場所と版。製品側の入口(aiterm-steer-delivery)が、呼べる相手かどうかを確かめるのに使う。 */
|
|
148
149
|
export function providerDescription() {
|
|
149
|
-
return { version: pkg.version, node:
|
|
150
|
+
return { version: pkg.version, node: liveNodeExecutable(), cli: fileURLToPath(import.meta.url) };
|
|
150
151
|
}
|
|
151
152
|
export async function runParentDelivery(argv) {
|
|
152
153
|
const [kind, command, ...args] = argv;
|
|
@@ -7,6 +7,7 @@ import { createRequire } from "node:module";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { defaultRuntimeErrorReportPaths, readBoundedFile, runtimeErrorReportingStatus } from "./runtime-error-os.js";
|
|
10
|
+
import { liveNodeExecutable } from "./live-node.js";
|
|
10
11
|
import { RuntimeErrorStore } from "./runtime-error-store.js";
|
|
11
12
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
12
13
|
const WORKER = fileURLToPath(new URL("./runtime-error-worker.js", import.meta.url));
|
|
@@ -252,7 +253,7 @@ export function triggerRuntimeErrorReport(env = process.env) {
|
|
|
252
253
|
try {
|
|
253
254
|
if (runtimeErrorReportingStatus(defaultRuntimeErrorReportPaths().reportingConfigPath) !== "enabled")
|
|
254
255
|
return false;
|
|
255
|
-
const child = spawn(
|
|
256
|
+
const child = spawn(liveNodeExecutable(), [WORKER, "report"], { stdio: "ignore", detached: true, windowsHide: true, env });
|
|
256
257
|
child.once("error", () => { });
|
|
257
258
|
child.unref();
|
|
258
259
|
return true;
|
|
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { defaultRuntimeErrorReportPaths, runtimeErrorReportingStatus } from "./runtime-error-os.js";
|
|
7
|
+
import { liveNodeExecutable } from "./live-node.js";
|
|
7
8
|
import { defaultRuntimeErrorPaths, windowsPrivateDaclCommand, expectedHostProfiles, readBoundedFile, processStartIdentity, forceKill } from "./runtime-error-os.js";
|
|
8
9
|
export { defaultRuntimeErrorPaths, windowsPrivateDaclCommand, windowsPrivateDaclVerifyCommand } from "./runtime-error-os.js";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -673,7 +674,7 @@ export function recordRuntimeError(code, options = {}) {
|
|
|
673
674
|
fixedStoreFailure(stderr);
|
|
674
675
|
} };
|
|
675
676
|
try {
|
|
676
|
-
const child = spawn(
|
|
677
|
+
const child = spawn(liveNodeExecutable(), [options.workerPath ?? WORKER, "record", code], {
|
|
677
678
|
stdio: "ignore", windowsHide: true, env: process.env,
|
|
678
679
|
});
|
|
679
680
|
const timer = setTimeout(() => { report(); forceKill(child); }, options.timeoutMs ?? defaultWorkerTimeoutMs("record"));
|
|
@@ -707,7 +708,7 @@ export async function runtimeErrorStoreDiagnostic(options = {}) {
|
|
|
707
708
|
} };
|
|
708
709
|
let child;
|
|
709
710
|
try {
|
|
710
|
-
child = spawn(
|
|
711
|
+
child = spawn(liveNodeExecutable(), [options.workerPath ?? WORKER, "diagnostic"], {
|
|
711
712
|
stdio: ["ignore", "pipe", "ignore"], windowsHide: true, env: process.env,
|
|
712
713
|
});
|
|
713
714
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// 利用者の設定file(harnessが既定を保存するfile)を、席の切り替えの前後で元の値へ戻すための部品(ADR 0114)。
|
|
2
|
+
// harnessの切り替えの命令は、席だけでなく利用者の既定も書き換える(Claude Codeの /fast、Codexの /fast、Grokの --model と /model)。
|
|
3
|
+
// 席の切り替えを席だけに留めるため、「読む → 切り替える → 戻す」を、processをまたぐ鍵で1つずつにする。
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { isDeepStrictEqual } from "node:util";
|
|
7
|
+
import { AitermError } from "./errors.js";
|
|
8
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
// 鍵を握ったままprocessが落ちた時に、後の人が引き取るまでの時間。1回の切り替え(画面の待ちと公式の口の呼び出し)より十分長く取る。
|
|
10
|
+
const LOCK_STALE_MS = 120_000;
|
|
11
|
+
const LOCK_WAIT_MS = 150_000;
|
|
12
|
+
const LOCK_POLL_MS = 120;
|
|
13
|
+
/**
|
|
14
|
+
* 設定fileを触る切り替えを、同じ端末の全てのAitermの間で1つずつにする。鍵は設定fileの隣に作る
|
|
15
|
+
* (Aitermの置き場はprocessごとに変えられるが、設定fileの場所は全員が同じに見る)。
|
|
16
|
+
* 設定fileの置き場が無い・書けない時は、harnessも書けないので鍵なしで通す。
|
|
17
|
+
*/
|
|
18
|
+
export async function withUserConfigLock(configPath, action) {
|
|
19
|
+
const lock = `${configPath}.aiterm-lock`;
|
|
20
|
+
if (!fs.existsSync(path.dirname(configPath)))
|
|
21
|
+
return action();
|
|
22
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
23
|
+
for (;;) {
|
|
24
|
+
try {
|
|
25
|
+
fs.mkdirSync(lock);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const code = error.code;
|
|
30
|
+
if (code !== "EEXIST")
|
|
31
|
+
return action();
|
|
32
|
+
}
|
|
33
|
+
let age = 0;
|
|
34
|
+
try {
|
|
35
|
+
age = Date.now() - fs.statSync(lock).mtimeMs;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (age > LOCK_STALE_MS) {
|
|
41
|
+
try {
|
|
42
|
+
fs.rmSync(lock, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
catch { /* 先に誰かが片付けた */ }
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (Date.now() > deadline) {
|
|
48
|
+
throw new AitermError(`USER_CONFIG_LOCK_TIMEOUT: ${configPath} を触る切り替えが、ほかの席で続いています。何も変えていません。`, 2);
|
|
49
|
+
}
|
|
50
|
+
await sleep(LOCK_POLL_MS + Math.floor(Math.random() * LOCK_POLL_MS));
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
try {
|
|
54
|
+
fs.writeFileSync(path.join(lock, "owner"), `${process.pid}\n`);
|
|
55
|
+
}
|
|
56
|
+
catch { /* 持ち主の控えは調べる時のため。無くても動く */ }
|
|
57
|
+
return await action();
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
try {
|
|
61
|
+
fs.rmSync(lock, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
catch { /* 残っても、古くなれば次の人が引き取る */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** fileの今の中身。無い・読めない時はnull。 */
|
|
67
|
+
export function readFileOrNull(file) {
|
|
68
|
+
try {
|
|
69
|
+
return fs.readFileSync(file);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function writeInPlace(file, body) {
|
|
76
|
+
let mode = 0o600;
|
|
77
|
+
try {
|
|
78
|
+
mode = fs.statSync(file).mode & 0o777;
|
|
79
|
+
}
|
|
80
|
+
catch { /* 無ければ0600で作る */ }
|
|
81
|
+
const temp = `${file}.aiterm-restore-${process.pid}`;
|
|
82
|
+
fs.writeFileSync(temp, body, { mode });
|
|
83
|
+
try {
|
|
84
|
+
fs.renameSync(temp, file);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// Windowsでは、ほかのprocessがfileを開いている間、置き換えに失敗する事がある。途中のfileを残さず、呼び手に次の回で試させる。
|
|
88
|
+
try {
|
|
89
|
+
fs.unlinkSync(temp);
|
|
90
|
+
}
|
|
91
|
+
catch { /* 残っても中身は前の値 */ }
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* JSONの設定fileの、最上位の項目(1つ、または複数)を、切り替えの前の値へ戻す。
|
|
97
|
+
* - 項目が前と同じで、ほかの項目の値も同じ(harnessが書き直して並びが変わっただけ)なら、前の中身をそのまま書き戻す。
|
|
98
|
+
* - ほかの項目が変わっていたら(ほかの人の書き込み、harnessの控えの更新)、その項目だけを直して、ほかは今のまま残す。
|
|
99
|
+
* - 今のfileが無い・JSONとして読めない時、前の中身が読めない時は触らない。
|
|
100
|
+
* 戻り値は、書き戻したかどうか。
|
|
101
|
+
*/
|
|
102
|
+
export function restoreJsonSetting(file, before, key) {
|
|
103
|
+
const keys = typeof key === "string" ? [key] : key;
|
|
104
|
+
const now = readFileOrNull(file);
|
|
105
|
+
if (now === null)
|
|
106
|
+
return false;
|
|
107
|
+
if (before !== null && now.equals(before))
|
|
108
|
+
return false;
|
|
109
|
+
let current;
|
|
110
|
+
let previous = null;
|
|
111
|
+
try {
|
|
112
|
+
current = JSON.parse(now.toString("utf8"));
|
|
113
|
+
if (before !== null)
|
|
114
|
+
previous = JSON.parse(before.toString("utf8"));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (!current || typeof current !== "object" || Array.isArray(current))
|
|
120
|
+
return false;
|
|
121
|
+
if (previous !== null && (typeof previous !== "object" || Array.isArray(previous)))
|
|
122
|
+
return false;
|
|
123
|
+
const edited = { ...current };
|
|
124
|
+
for (const name of keys) {
|
|
125
|
+
if (previous !== null && Object.hasOwn(previous, name))
|
|
126
|
+
edited[name] = previous[name];
|
|
127
|
+
else
|
|
128
|
+
delete edited[name];
|
|
129
|
+
}
|
|
130
|
+
if (before !== null && isDeepStrictEqual(edited, previous)) {
|
|
131
|
+
writeInPlace(file, before);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (isDeepStrictEqual(edited, current))
|
|
135
|
+
return false;
|
|
136
|
+
writeInPlace(file, `${JSON.stringify(edited, null, 2)}\n`);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
package/docs/DESIGN.md
CHANGED
|
@@ -63,6 +63,16 @@ agentの席への`pty_send`は、任意の`preface`(本文の前に置く1行
|
|
|
63
63
|
空行と本文を1回の貼り付けで入れる。貼り付けた長い文をClaude Codeが`<pasted_content>`で包んでも、前置きは包みの外に出る。
|
|
64
64
|
ほかのharnessの席へは1つにつないで貼る。`preface`は入力欄がそのまま読むので、1行・200字以内・制御文字なし・行頭が記号や空白でない・
|
|
65
65
|
`@`を含まない文だけを通し、外れた時は打鍵前に断る。
|
|
66
|
+
呼び手が`preface`を渡さない時も、Claude Codeの席へ送る文が包まれる長さ(改行3つ以上か800字以上。`claudeWrapsPaste`)なら、Aitermが既定の1行
|
|
67
|
+
(`AGENT_DEFAULT_PREFACE`。出所だけを言う)を同じ形で入れる。新しいturn・差し込み・起動時の文で同じ。起動時に席へ渡す文で、この1行の見分け方を教える(ADR 0116)。
|
|
68
|
+
|
|
69
|
+
Claude Codeの席へ送る前に、今の画面が選ぶ画面(最後の行が`Enter to confirm · Esc to cancel`で、上に番号の行がある。`claudeLiveSelectMenu`)で
|
|
70
|
+
ないかを読む。利用枠に当たったClaude Codeが自分で開く`/rate-limit-options`の画面は、Escで閉じてから送る。見覚えの無い選ぶ画面には何も打たずに
|
|
71
|
+
断る。選ぶ画面は貼り付けを捨ててEnterだけを読み、打鍵の数字と「j」「k」を選択の操作として読むので、そのまま送ると文が消える(ADR 0117)。
|
|
72
|
+
|
|
73
|
+
席のprocessの環境には、起動の印(`AITERM_AGENT_LAUNCH_ID`ほか`AITERM_AGENT_*`)を入れる。印は席が起こした子にも継がれる。起動の時点で会話IDが
|
|
74
|
+
決まるClaude CodeとGrokの席には、席の会話ID(`AITERM_AGENT_VENDOR_SESSION_ID`)も入れ、席そのものの会話かを値で見分けられるようにする(ADR 0118)。
|
|
75
|
+
素の端末(`pty_open`)には`AITERM_AGENT_*`を入れない。
|
|
66
76
|
|
|
67
77
|
Codexは回答を流している間、画面に動作中の行を出さない。`pty_send`の振り分けは、Codexの席に限り、画面が入力待ちでも、この起動の記録の
|
|
68
78
|
最後の境界がturnの開始で、processが生きていて、0.4秒おいて見直しても記録が同じturnのままで画面が動いている時を、動いていると数える
|
|
@@ -171,6 +181,7 @@ hookコマンドはCodexが使うshellで評価される。Windowsでは引用
|
|
|
171
181
|
承認省略flagを恒久設定へ書かない。利用者の別hookを承認しない。
|
|
172
182
|
選択と配送の所有記録は`~/.config/aiterm-mcp/codex-parent-hooks/`に置く。
|
|
173
183
|
Nodeは`src/setup-node.ts`でHomebrewの同じformulaの`opt`へ正規化し、更新で消えるCellar実体を保存しない。
|
|
184
|
+
動いているprocessが自分のNodeを起こす時・呼び手へ渡す時も、`src/live-node.ts`で今ある場所を引く(`process.execPath`を直接使わない。ADR 0113)。
|
|
174
185
|
|
|
175
186
|
新規の起動差し替えは作らない。旧中継の設定は新しいhookの確認後に従来の解除処理で復元する。
|
|
176
187
|
macOSの専用LaunchAgent、Windowsのユーザー環境変数、所有外の値の保持は旧adapterが担う。
|
package/docs/RELEASE.md
CHANGED
|
@@ -11,6 +11,9 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
|
|
|
11
11
|
- 版番号だけの変更はJSONの実差分で識別し、Linuxでbuildと配布metadata・pack・文書確認を行う。依存や実行設定の変更は省略しない。
|
|
12
12
|
文書だけなら文書検査、実装と文書の混在なら関連試験と文書検査を行う。
|
|
13
13
|
- 週1回の定期実行(月曜 03:00 JST)と手動実行は、指定された環境で全テストを回す。定期実行の対象は3環境である。
|
|
14
|
+
- pushは、同じcommitの全テスト(手動実行か定期実行)がもう通っている環境を流さない。枝で手動実行を通してからmainへ早送りすると、
|
|
15
|
+
mainのpushは変更分類と関門だけで終わる。通っている環境が一部の時は、残りの環境だけを流す。GitHubへ聞けない時は、選んだ環境を全部流す。
|
|
16
|
+
同じcommitのpush・pull requestの成功は、試験を絞っている事があるので数えない(ADR 0115)。
|
|
14
17
|
- tag push: 所有確認と、tagged commitが`origin/main`の祖先であることの確認だけを行い、npmへprovenance付きで
|
|
15
18
|
publishする。同じcommitのmain CIの結果は待たない。
|
|
16
19
|
|
|
@@ -19,6 +22,8 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
|
|
|
19
22
|
1. 変更に直結するfocused testを手元で通す。full regressionは手元で回さず、CIに任せる。
|
|
20
23
|
個別試験も`node --import ./test/seat-env.mjs --test ...`で起動する。稼働中の席の保存場所を試験へ継承しない。
|
|
21
24
|
2. `CHANGELOG.md`の`## [Unreleased]`へ内容を書き、mainへcommitしてpushする。
|
|
25
|
+
3環境の全テストを先に通しておく時は、枝をpushして手動実行(`gh workflow run ci.yml --ref <枝> -f environment=all -f comparison_base=<前の公開commit>`)を
|
|
26
|
+
1回流し、通ったcommitをそのままmainへ早送りする。Windowsの全テストは1回で済む。
|
|
22
27
|
3. 一回で公開する。
|
|
23
28
|
|
|
24
29
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"mcpName": "io.github.kitepon/aiterm-mcp",
|
|
5
5
|
"description": "Persistent terminal MCP with one harness-based launcher for Claude Code, Codex CLI, Grok CLI, and Cursor Agent CLI, plus durable PTYs for SSH, containers, and REPLs.",
|
|
6
6
|
"keywords": [
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
75
|
-
"aiterm-steer-delivery": "^0.4.
|
|
75
|
+
"aiterm-steer-delivery": "^0.4.6",
|
|
76
76
|
"ws": "8.21.3",
|
|
77
77
|
"zod": "^4.4.3"
|
|
78
78
|
},
|