aiterm-mcp 0.61.14 → 0.61.16
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 +15 -1
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/execution-service.js +127 -72
- package/docs/DESIGN.md +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.61.16] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Registration and returned launch/send receipt binding perform asynchronous native observation outside the shared metadata mutex. Registration rechecks current admission before adding its record; receipt binding rechecks the exact original issued slot before accepting it. Slow observation no longer blocks unrelated room targets, and uncertain actions remain unreplayed.
|
|
15
|
+
|
|
16
|
+
## [0.61.15] - 2026-10-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Completion checks observe the original request boundary outside the shared metadata mutex, then revalidate the unchanged original record and all uncertainty/slot gates before committing. Slow transcript/native completion observation no longer blocks unrelated admission; unknown sends are never reconciled or replayed.
|
|
21
|
+
|
|
10
22
|
## [0.61.14] - 2026-10-24
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -2842,7 +2854,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
|
|
|
2842
2854
|
`ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
|
|
2843
2855
|
provenance.
|
|
2844
2856
|
|
|
2845
|
-
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.
|
|
2857
|
+
[Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.16...HEAD
|
|
2858
|
+
[0.61.16]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.15...v0.61.16
|
|
2859
|
+
[0.61.15]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.14...v0.61.15
|
|
2846
2860
|
[0.61.14]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.13...v0.61.14
|
|
2847
2861
|
[0.61.13]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.12...v0.61.13
|
|
2848
2862
|
[0.61.12]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.11...v0.61.12
|
package/README.ja.md
CHANGED
|
@@ -209,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
|
|
|
209
209
|
場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
|
|
210
210
|
Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
|
|
211
211
|
|
|
212
|
-
**状態:** 開発継続中 · 現行公開版 **v0.61.
|
|
212
|
+
**状態:** 開発継続中 · 現行公開版 **v0.61.16** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
|
|
213
213
|
|
|
214
214
|
### 更新と巻き戻し
|
|
215
215
|
|
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ collection is off by default and performs no network I/O. It ships via
|
|
|
227
227
|
tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
|
|
228
228
|
Release re-registers the Official MCP Registry entry.
|
|
229
229
|
|
|
230
|
-
**Status:** actively maintained · current public release **v0.61.
|
|
230
|
+
**Status:** actively maintained · current public release **v0.61.16** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
|
|
231
231
|
|
|
232
232
|
### Update and rollback
|
|
233
233
|
|
|
@@ -701,7 +701,7 @@ export class ExecutionService {
|
|
|
701
701
|
}, { readOnly: true });
|
|
702
702
|
}
|
|
703
703
|
async register(args, context, closeExpectation) {
|
|
704
|
-
|
|
704
|
+
const previous = (state) => {
|
|
705
705
|
if (closeExpectation)
|
|
706
706
|
assertCloseLaunchExpectation(state, closeExpectation, args.reservation.request);
|
|
707
707
|
const previous = state.records.find(record => same(record.reservation.request, args.reservation.request));
|
|
@@ -712,20 +712,32 @@ export class ExecutionService {
|
|
|
712
712
|
}
|
|
713
713
|
if (interruptionBlocksRequest(state, args.reservation.request))
|
|
714
714
|
throw new ExecutionError("EXECUTION_STOPPING");
|
|
715
|
-
|
|
716
|
-
|
|
715
|
+
return undefined;
|
|
716
|
+
};
|
|
717
|
+
const found = await this.store.transaction(previous, { readOnly: true });
|
|
718
|
+
if (found)
|
|
719
|
+
return found;
|
|
720
|
+
// Registration has no native effect. Observe outside the shared writer
|
|
721
|
+
// mutex, then recheck current admission metadata before adding the record.
|
|
722
|
+
// Actual invocation still proves the generation at its effect gate.
|
|
723
|
+
const source = assessExecutionSource(args.reservation.source, await this.runtime.sourceEvidence(args, context));
|
|
724
|
+
if (source.status === "stale")
|
|
725
|
+
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
726
|
+
if (args.reservation.source.launch && source.status !== "verified") {
|
|
727
|
+
const currentSource = await this.runtime.observe(args.reservation.source.launch.session_id);
|
|
728
|
+
if (currentSource.state === "absent" || (currentSource.state === "present" && currentSource.launch
|
|
729
|
+
&& !same(currentSource.launch, args.reservation.source.launch)))
|
|
717
730
|
throw new ExecutionError("EXECUTION_STALE_GENERATION");
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
731
|
+
}
|
|
732
|
+
const target = args.reservation.target;
|
|
733
|
+
const current = structuredClone(await this.runtime.observe(target.session_id));
|
|
734
|
+
return this.store.transaction(state => {
|
|
735
|
+
const found = previous(state);
|
|
736
|
+
if (found)
|
|
737
|
+
return found;
|
|
724
738
|
sourceAdmission(state, source.point, args.source_session_id);
|
|
725
|
-
const target = args.reservation.target;
|
|
726
739
|
if (targetBlocked(state, args.reservation.request, target.session_id, target.expected_launch_id))
|
|
727
740
|
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
728
|
-
const current = await this.runtime.observe(target.session_id);
|
|
729
741
|
this.expectTarget(current, target.expected_launch_id, target.session_id);
|
|
730
742
|
if (current.state === "present" && stopped(state, current.launch))
|
|
731
743
|
throw new ExecutionError("EXECUTION_STOPPING");
|
|
@@ -998,44 +1010,56 @@ export class ExecutionService {
|
|
|
998
1010
|
}
|
|
999
1011
|
// Parsed above after issuance; null is supported only for an unissued noop.
|
|
1000
1012
|
const targetPoint = outcome.target;
|
|
1001
|
-
const record = await
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const current = await this.runtime.observe(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1013
|
+
const record = await (async () => {
|
|
1014
|
+
const originalSlot = await this.store.transaction(state => {
|
|
1015
|
+
const record = find(state, request);
|
|
1016
|
+
const slot = state.slots.find(value => same(value.request, request));
|
|
1017
|
+
if (!slot || slot.stage !== stage || record.operations[stage] !== "issued")
|
|
1018
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1019
|
+
return slot;
|
|
1020
|
+
}, { readOnly: true });
|
|
1021
|
+
// A returned action must not monopolize all targets while its native
|
|
1022
|
+
// generation is observed. Keep its exact issued slot until commit, and
|
|
1023
|
+
// reject any concurrent uncertainty/replacement without replaying it.
|
|
1024
|
+
const current = structuredClone(await this.runtime.observe(originalSlot.session_id));
|
|
1025
|
+
return this.store.transaction(state => {
|
|
1026
|
+
if (closeExpectation)
|
|
1027
|
+
assertCloseLaunchExpectation(state, closeExpectation, request);
|
|
1028
|
+
const record = find(state, request);
|
|
1029
|
+
const slot = state.slots.find(value => same(value.request, request));
|
|
1030
|
+
if (!slot || !same(slot, originalSlot) || slot.stage !== stage || record.operations[stage] !== "issued")
|
|
1031
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1032
|
+
const expected = slot.expected_launch_id;
|
|
1033
|
+
if (targetPoint.launch.session_id !== slot.session_id
|
|
1034
|
+
|| (expected !== null && targetPoint.launch.launch_id !== expected))
|
|
1035
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1036
|
+
const closing = request.kind === "mcp" && ["pty_close", "agent_auth.cancel"].includes(request.tool);
|
|
1037
|
+
if (closing ? current.state !== "absent" : current.state !== "present" || !same(current.launch, targetPoint.launch))
|
|
1038
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1039
|
+
record.operations[stage] = "accepted";
|
|
1040
|
+
// A launch with an initial prompt already performed this reference's
|
|
1041
|
+
// send. Only an empty launch may be followed by a separate pty_send.
|
|
1042
|
+
if (stage === "launch" && targetPoint.boundary)
|
|
1043
|
+
record.operations.send = "accepted";
|
|
1044
|
+
record.relation.target = targetPoint;
|
|
1045
|
+
record.relation.phase = "invoked";
|
|
1046
|
+
record.relation.completion = closing ? "interrupted" : "open";
|
|
1047
|
+
if (closing)
|
|
1048
|
+
record.relation.target_fence = "closed";
|
|
1049
|
+
if (state.stopping.some(value => same(value, record.relation.source?.launch)) && !state.stopping.some(value => same(value, targetPoint.launch)))
|
|
1050
|
+
state.stopping.push(targetPoint.launch);
|
|
1051
|
+
state.slots = state.slots.filter(value => !same(value.request, request));
|
|
1052
|
+
if (closing) {
|
|
1053
|
+
// Actual management close plus confirmed absence releases only these
|
|
1054
|
+
// failed send admissions. Old callbacks still fail their missing-slot
|
|
1055
|
+
// guard, and their records remain unknown and cannot be invoked again.
|
|
1056
|
+
const recovered = recoverableCloseSlots(state, request, slot.session_id, expected);
|
|
1057
|
+
state.slots = state.slots.filter(value => !recovered.includes(value));
|
|
1058
|
+
this.interruptRelations(state, targetPoint.launch);
|
|
1059
|
+
}
|
|
1060
|
+
return view(state, record);
|
|
1061
|
+
});
|
|
1062
|
+
})().catch(async () => {
|
|
1039
1063
|
// The side effect already returned, but its result could not be safely
|
|
1040
1064
|
// bound/committed. Absence/replacement is not permission to replay it.
|
|
1041
1065
|
try {
|
|
@@ -1113,6 +1137,8 @@ export class ExecutionService {
|
|
|
1113
1137
|
});
|
|
1114
1138
|
}
|
|
1115
1139
|
async finish(args) {
|
|
1140
|
+
if (args.evidence.kind === "completion")
|
|
1141
|
+
return this.finishCompletion(args.request, args.evidence.target);
|
|
1116
1142
|
return this.store.transaction(async (state) => {
|
|
1117
1143
|
const record = find(state, args.request);
|
|
1118
1144
|
if (record.operations.launch === "issued" || record.operations.send === "issued"
|
|
@@ -1134,34 +1160,63 @@ export class ExecutionService {
|
|
|
1134
1160
|
const target = record.relation.target;
|
|
1135
1161
|
if (!target)
|
|
1136
1162
|
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1137
|
-
if (args.evidence.
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
record.relation.target_fence = pendingParentReturn(state, args.request) ? "unknown"
|
|
1147
|
-
: observed.state === "completed" ? "closed" : observed.fence;
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
if (!same(target.launch, args.evidence.target))
|
|
1151
|
-
throw new ExecutionError("EXECUTION_CONFLICT", "possible");
|
|
1152
|
-
if (record.relation.completion === "interrupted" && record.relation.target_fence === "closed")
|
|
1153
|
-
return view(state, record);
|
|
1154
|
-
const observed = await this.runtime.observe(target.launch.session_id);
|
|
1155
|
-
if (observed.state !== "absent" || state.slots.some(slot => slot.session_id === target.launch.session_id))
|
|
1156
|
-
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1157
|
-
record.relation.completion = "interrupted";
|
|
1158
|
-
record.relation.target_fence = "closed";
|
|
1159
|
-
}
|
|
1163
|
+
if (!same(target.launch, args.evidence.target))
|
|
1164
|
+
throw new ExecutionError("EXECUTION_CONFLICT", "possible");
|
|
1165
|
+
if (record.relation.completion === "interrupted" && record.relation.target_fence === "closed")
|
|
1166
|
+
return view(state, record);
|
|
1167
|
+
const observed = await this.runtime.observe(target.launch.session_id);
|
|
1168
|
+
if (observed.state !== "absent" || state.slots.some(slot => slot.session_id === target.launch.session_id))
|
|
1169
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1170
|
+
record.relation.completion = "interrupted";
|
|
1171
|
+
record.relation.target_fence = "closed";
|
|
1160
1172
|
}
|
|
1161
1173
|
retireSettledBranches(state);
|
|
1162
1174
|
return view(state, record);
|
|
1163
1175
|
});
|
|
1164
1176
|
}
|
|
1177
|
+
/** Completion evidence is for one fixed historical request/Point. Native
|
|
1178
|
+
* transcript/generation observation must not hold the global writer mutex.
|
|
1179
|
+
* Commit only if the original record and all uncertainty gates still match;
|
|
1180
|
+
* an unrelated writer is allowed to progress while evidence is observed.
|
|
1181
|
+
*/
|
|
1182
|
+
async finishCompletion(request, target) {
|
|
1183
|
+
const eligible = (state) => {
|
|
1184
|
+
const record = find(state, request);
|
|
1185
|
+
if ([record.operations.launch, record.operations.send].some(stage => stage === "issued" || stage === "unknown")
|
|
1186
|
+
|| state.slots.some(slot => slot.request && same(slot.request, request))
|
|
1187
|
+
|| openParentEffect(state, request))
|
|
1188
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1189
|
+
if (!record.relation.target)
|
|
1190
|
+
throw new ExecutionError("EXECUTION_CONFLICT");
|
|
1191
|
+
if (!record.relation.target.boundary || !same(record.relation.target, target))
|
|
1192
|
+
throw new ExecutionError("EXECUTION_CONFLICT", "possible");
|
|
1193
|
+
return record;
|
|
1194
|
+
};
|
|
1195
|
+
const settled = (record) => record.relation.completion === "completed" && record.relation.target_fence === "closed";
|
|
1196
|
+
const initial = await this.store.transaction(state => {
|
|
1197
|
+
const record = eligible(state);
|
|
1198
|
+
return { record, result: view(state, record) };
|
|
1199
|
+
}, { readOnly: true });
|
|
1200
|
+
if (settled(initial.record))
|
|
1201
|
+
return initial.result;
|
|
1202
|
+
const observed = { ...await this.runtime.completion(executionPointSchema.parse(target)) };
|
|
1203
|
+
if (observed.state === "unknown")
|
|
1204
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1205
|
+
return this.store.transaction(state => {
|
|
1206
|
+
const record = eligible(state);
|
|
1207
|
+
if (settled(record))
|
|
1208
|
+
return view(state, record);
|
|
1209
|
+
// Do not overwrite a concurrent cancellation, target rebinding, phase
|
|
1210
|
+
// change or interruption with an earlier read's completion evidence.
|
|
1211
|
+
if (!same(record, initial.record))
|
|
1212
|
+
throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
|
|
1213
|
+
record.relation.completion = observed.state;
|
|
1214
|
+
record.relation.target_fence = pendingParentReturn(state, request) ? "unknown"
|
|
1215
|
+
: observed.state === "completed" ? "closed" : observed.fence;
|
|
1216
|
+
retireSettledBranches(state);
|
|
1217
|
+
return view(state, record);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1165
1220
|
/** Adapter for existing restart/configuration/close paths. It shares the
|
|
1166
1221
|
* same per-seat issuance slots as dispatch and stop, without creating a job ID.
|
|
1167
1222
|
* Callers must not wrap a multi-step restart in one unobserved callback:
|
package/docs/DESIGN.md
CHANGED
|
@@ -62,6 +62,14 @@ source/target世代の観測は保持せず、従来どおり各受付で再照
|
|
|
62
62
|
実killの直前にもruntimeの同期世代読取を通し、scope/actor/期待launchの交代を拒否する。
|
|
63
63
|
注入runtimeのclose欠測をcore既定処理で補わず、作用後の応答欠測は消失観測があってもunknown slotを保持する。
|
|
64
64
|
完了の照会点は固定し、終了fenceの非同期応答後にも現在世代を確認する。実受取番/終了fenceの証拠adapterは未了。
|
|
65
|
+
通常のcompletion終了確認は元request/Pointを共有lock内で固定し、native/transcript観測をlock外で行う。
|
|
66
|
+
反映時に同じrecord全体・issued/unknown・元slot・親作用を再照合し、途中変更があれば反映しない。
|
|
67
|
+
親返却のpending判定は反映時のstateで行い、元unknownの解除や送信の再実行には使わない。
|
|
68
|
+
実行登録も同じrequestの既存予約を短い共有lock内で確認してから、source/targetの非同期観測をlock外で行う。
|
|
69
|
+
登録反映時に同じ予約・取消・source/targetの受付条件を再確認し、実作用直前の同期世代gateは維持する。
|
|
70
|
+
launch/sendの返却結果は元issued slotを固定してlock外で世代観測し、同じslotとissued状態だけに結合する。
|
|
71
|
+
途中のunknown化・slot交代・観測した世代の不一致はpossibleとして保持し、元作用を再実行しない。
|
|
72
|
+
遅延した要求取消は本物の受付結果を消さず、共有lock内へ非同期の観測待ちを戻さない。
|
|
65
73
|
private producer/worker factoryを構築したprocessではnative effect scopeを必須にする。
|
|
66
74
|
coreの公開変更入口も、打鍵や設定・認証・lock/metadata準備に入る前に対象のrequest contextを要求する。
|
|
67
75
|
この入口確認はissuedへの昇格ではない。実作用時には既存の同期受付を別に通す。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiterm-mcp",
|
|
3
|
-
"version": "0.61.
|
|
3
|
+
"version": "0.61.16",
|
|
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": [
|