aiterm-mcp 0.61.6 → 0.61.8

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 CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.61.8] - 2026-10-23
11
+
12
+ ### Fixed
13
+
14
+ - Repeating empty-launch reconciliation now rechecks the current managed generation, no-input readiness and competing session operations before returning an accepted launch with an unissued send. A historical accepted receipt cannot authorize clearing a newer generation's launch uncertainty. Rejection preserves the existing graph and issues no native input.
15
+
16
+ ## [0.61.7] - 2026-10-23
17
+
18
+ ### Fixed
19
+
20
+ - Native host `agent_auth` now admits automatic-name and explicit starts, authenticated no-op starts, session status/onboarding, absent-session receipts and cancellation through the existing v1 contract. Authentication PTYs retain their actual creation native/server identity and generation; only those proven PTYs accept ref-less host input/keys. The dispatcher uses the serving SDK RPC identity, without synthetic delivery references or read-only downgrade.
21
+ - BellTeam model, effort and fast changes are admitted as native management RPCs bound to the existing session generation. Every native stage retains its normal stop/slot/generation checks, and partial failures retain possible effects. Fast-state metadata is revalidated before writing after asynchronous CLI observations.
22
+
23
+ ### Added
24
+
25
+ - The opt-in native execution entry can explicitly reconcile a returned unknown, empty launch against its original native/server identity and current ready managed generation through `agent_execution/reconcile_launch`. It preserves the existing request and generation, issues no native input and permits a later ordinary send only through its usual admission. Issued RPCs and uncertain input or generations remain blocked; late callbacks cannot overwrite the reconciled stage.
26
+
10
27
  ## [0.61.6] - 2026-10-22
11
28
 
12
29
  ### Fixed
@@ -2742,7 +2759,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2742
2759
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2743
2760
  provenance.
2744
2761
 
2745
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.6...HEAD
2762
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.8...HEAD
2763
+ [0.61.8]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.7...v0.61.8
2764
+ [0.61.7]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.6...v0.61.7
2746
2765
  [0.61.6]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.5...v0.61.6
2747
2766
  [0.61.5]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.4...v0.61.5
2748
2767
  [0.61.4]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.3...v0.61.4
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.6** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.61.8** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
213
213
 
214
214
  ### 更新と巻き戻し
215
215
 
@@ -734,6 +734,13 @@ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/
734
734
 
735
735
  1回の `pty_send` が受理する本文はUTF-8で最大64KiB。同一sessionへの送信はaiterm processをまたいで直列化し、chunk同士の混線を防ぐ。全OSで長いPTY入力の欠落を避けるためplatformのmultiplexerへUTF-8境界を壊さない256-byte単位でpasteし、chunk間に10msのdrain間隔を置く。POSIX shellまたはPowerShellが前面にいる時のsanitize済み複数行は、改行を含まない単一入力へ符号化する。POSIXは`eval`、PowerShellはUTF-8のBase64を復元したscriptblockのdot-sourceを使い、変数と作業場所を現在のshellに保つ。shellがscript全体を所有してから先頭行を実行するため、途中で起動したpager/REPLが後続行を対話キーとして奪わない。単一行、`raw:true`、非shell前面は従来どおり直接PTYへpasteする。agent dispatchは本文全体を`ESC[200~/201~`で1回だけ包み、包んだ全体を同じchunkで流す。agent TUIには1回の貼り付けに見えるので画像pathが貼り付けの間で切れず、chunk投入中のキー解釈による語中文字化け・submit取り落としも抑える。途中chunkが失敗した場合は部分送信済みであることを明示し、自動でEnterを押さない。送信processの異常終了でlockが残った場合は送信前にfail-closedする。`pty_list`で対象sessionを確認し、`pty_close`で閉じてから同じsession IDを作り直す。公開の一括停止toolは存在しない。 POSIXの明示native execution構成では、unknown sendになった対象1席も`pty_close`で閉じて再作成できる。元requestはunknownのまま保持し、再送しない。発行中の操作、管理close自体のunknown、native identityが不明な席は引き続き拒否する。
736
736
 
737
+ BellTeamのmodel・effort・fast変更も、既存session世代に結合したnative管理要求で受け付ける。途中で世代・受付枠・停止状態が変われば、それ以降の打鍵を止める。途中まで変更した結果は作用不明のまま保持し、自動でやり直さない。
738
+
739
+ POSIXの明示native入口でも、`agent_auth`の名前なし開始、認証済みで何も起こさない開始、既存v1の確認・初回案内・取消を受け付ける。認証PTYは実際の作成元nativeと世代へ結合する。BellTeamの参照なし入力・キーは、この証拠がある認証PTYにだけ許可し、通常agentの本文には配送参照を引き続き必要とする。作成証拠のない旧認証metadataを自動登録しない。
740
+
741
+ この明示入口では `agent_execution` の `reconcile_launch` に元の `request` と正確な `target: {session_id, launch_id}` を渡し、結果不明の空起動を既存世代へ結合できる。保存したnative/serverと現在のmanaged世代、raw状態で初期promptなし、生存して入力待ちのharnessをmetadata受付内で照合し、該当するunknown launch枠だけを解放する。受付済みでも本文未発行なら、照合を繰り返すたびに現在世代・入力待ち・競合操作なしを再確認する。保存した受付結果だけで後の世代を確認済みとしない。起動・打鍵・close・本文再送は行わない。issuedのRPC、入力状態不明、世代交代や世代未確認は拒否する。後続本文は元参照で通常のsend受付を通して一度だけ発行する。照合成功は失われた元RPC receiptの回収を意味しない。
742
+
743
+
737
744
  ## 人が覗く
738
745
 
739
746
  POSIXの明示native実行構成では、検証済みcallerへ通常メッセージが届く間も、そのcallerから別席への送信を継続できる。callerの世代や各requestのslotは書き換えない。agentの貼付け・Enterはmetadata mutexの競合を入力前に待ち、不明な操作・世代交代・停止では入力を止める。BellTeamは閉じた席をpromptなしの明示管理`agent_launch`で再作成できる。promptを送る呼出しには元の実行参照が必要である。
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.6** · 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).
230
+ **Status:** actively maintained · current public release **v0.61.8** · 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
 
@@ -796,9 +796,16 @@ Each `pty_send` accepts at most 64 KiB of UTF-8 text. Sends to the same session
796
796
 
797
797
  In the opt-in POSIX native execution entry, an ordinary message arriving at a verified caller can overlap that caller's outgoing send. It does not replace the caller's generation or clear either request's slot. Agent paste/submit segments wait for metadata mutex contention before input; uncertain operations, generation changes and stops still prevent input. BellTeam may explicitly recreate a closed session with a promptless management `agent_launch`; sending a prompt still requires its original execution reference.
798
798
 
799
+ An explicit `agent_execution` action `reconcile_launch` can bind an existing empty launch after a lost launch result in this opt-in entry. Supply the original `request` and exact `target: {session_id, launch_id}`. The server checks the original native/server identity, current managed generation, raw no-prompt state and a live ready harness under metadata admission, then releases only that unknown launch slot. Repeating an accepted empty launch before its send also rechecks the current generation, readiness and absence of competing session operations; a saved acceptance alone cannot confirm a later generation. It never launches, types, closes or resends. `issued` RPCs, unknown input and changed or unverified generations remain blocked. Any subsequent body uses the original reference through normal send admission once; a successful reconciliation does not prove the lost RPC receipt.
800
+
801
+
799
802
  Sessions live on a shared tmux socket on POSIX or a shared psmux namespace on native Windows. The attach line printed by `pty_open` and `agent_launch` lets a human attach to the same terminal and intervene, including a Claude/Codex/Grok/Cursor harness session: `tmux -S … attach -t <id>` on POSIX, or `psmux -L <namespace> attach -t <id>` on native Windows.
800
803
  On POSIX, the first `agent_launch` can create the tmux server when its socket does not yet exist. Aiterm verifies the exact created session and server before accepting that launch; an uncertain result is reported without retrying the input.
801
804
 
805
+ BellTeam model, effort and fast changes use a native management request bound to the existing session generation. Later stages stop if that generation, its admission slot or stop state changes. A partial change remains an uncertain effect and must not be blindly repeated.
806
+
807
+ In the opt-in POSIX native entry, `agent_auth` also supports omitted session names, authenticated no-op starts and the normal v1 status/onboarding/cancel receipts. Authentication PTYs are bound to their actual native creator and generation. Ref-less BellTeam input or keys are allowed only for those proven authentication PTYs; ordinary agent messages still require their delivery reference. Legacy authentication metadata without creator proof is not automatically enrolled.
808
+
802
809
  ## Requirements
803
810
 
804
811
  - **Node.js >= 18**
package/dist/core.js CHANGED
@@ -15,7 +15,7 @@ import { createHash, randomBytes } from "node:crypto";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import * as rtk from "./rtk.js";
17
17
  import { closeExpectedExecution, parseExecutionNativeSessions, executionNativeBindingSchema } from "./execution-close.js";
18
- import { executionPointSchema } from "./execution-contract.js";
18
+ import { executionLaunchSchema, executionPointSchema } from "./execution-contract.js";
19
19
  import { withExecutionInterruptEffectScope, bindExecutionNativeTarget, beforeExecutionEffect, bindExecutionCreatedLaunch, executionCreatedNativeBinding, hasExecutionCreationScope, assertExecutionContext, withExecutionPreparation, captureExecutionSendBoundary, readExecutionSendBoundary, executionManagementCloseTarget, withExecutionNativeAdmission } from "./execution-effect.js";
20
20
  import { ExecutionError } from "./execution-wire.js";
21
21
  import { readExecutionParentProcess } from "./execution-parent-process.js";
@@ -1678,17 +1678,59 @@ export function observeExecutionSessionGeneration(name) {
1678
1678
  const native = before.sessions.find(value => value.session_id === name);
1679
1679
  if (!native)
1680
1680
  return result(false, null);
1681
- const meta = tryLoadAgentMetadata(name), proof = meta?.execution_native;
1681
+ const meta = tryLoadAgentMetadata(name) ?? readExecutionAuthMetadata(name), proof = meta?.execution_native;
1682
1682
  if (!meta || !proof || !before.server || proof.native_id !== native.native_id
1683
1683
  || !isDeepStrictEqual(proof.server, before.server))
1684
1684
  return result(true, null);
1685
- const after = observeExecutionNativeCreationScope(), last = tryLoadAgentMetadata(name);
1685
+ const after = observeExecutionNativeCreationScope(), last = tryLoadAgentMetadata(name) ?? readExecutionAuthMetadata(name);
1686
1686
  if (after.state !== "known" || !isDeepStrictEqual(after.server, proof.server)
1687
1687
  || after.sessions.find(value => value.session_id === name)?.native_id !== proof.native_id
1688
- || last?.launch_id !== meta.launch_id || !isDeepStrictEqual(last.execution_native, proof))
1688
+ || last?.launch_id !== meta.launch_id || !isDeepStrictEqual(last?.execution_native, proof))
1689
1689
  return result(true, null);
1690
1690
  return result(true, meta.launch_id);
1691
1691
  }
1692
+ /** Only native-stamped authentication PTYs qualify for host auth input. */
1693
+ export function observeExecutionAuthSessionGeneration(name) {
1694
+ const current = observeExecutionSessionGeneration(name), meta = readExecutionAuthMetadata(name);
1695
+ return { ...current, launch_id: current.launch_id && current.launch_id === meta?.launch_id && !tryLoadAgentMetadata(name)
1696
+ ? current.launch_id : null };
1697
+ }
1698
+ /** Allocate only a terminal name. The serving RPC then reserves it and the
1699
+ * actual native creation receipt supplies its identity; no existing seat is enrolled. */
1700
+ export function selectExecutionAuthSessionName() { return "auth-" + randomBytes(12).toString("hex"); }
1701
+ /** Read-only proof for reconciling a created, empty launch. Inspect raw prompt
1702
+ * state: legacy/default normalization must never prove that input was absent. */
1703
+ export function observeExecutionCreatedLaunch(target) {
1704
+ target = executionLaunchSchema.parse(target);
1705
+ const unknown = () => { throw new ExecutionError("EXECUTION_UNCONFIRMED"); };
1706
+ const generation = () => {
1707
+ const value = observeExecutionSessionGeneration(target.session_id);
1708
+ if (!value.exists || value.launch_id !== target.launch_id)
1709
+ unknown();
1710
+ };
1711
+ const read = () => {
1712
+ let raw;
1713
+ try {
1714
+ raw = JSON.parse(fs.readFileSync(agentMetadataPath(target.session_id, target.launch_id), "utf8"));
1715
+ }
1716
+ catch {
1717
+ return unknown();
1718
+ }
1719
+ const proof = executionNativeBindingSchema.safeParse(raw.execution_native);
1720
+ if (raw.aiterm_session !== target.session_id || raw.launch_id !== target.launch_id
1721
+ || raw.initial_prompt !== "none" || raw.initial_prompt_delivery != null || !proof.success)
1722
+ return unknown();
1723
+ return proof.data;
1724
+ };
1725
+ generation();
1726
+ const native = read(), live = observeSession(target.session_id);
1727
+ if (live.launch_id !== target.launch_id || live.harness_alive !== true || live.harness_process === null || live.state !== "idle")
1728
+ unknown();
1729
+ generation();
1730
+ if (!isDeepStrictEqual(read(), native))
1731
+ unknown();
1732
+ return { target, native, initial_prompt: "none", initial_prompt_delivery: null, harness_alive: true, ready: true };
1733
+ }
1692
1734
  /** Private source-provenance snapshot. It deliberately omits screen, argv,
1693
1735
  * environment and cached vendor_session_id. No public tool is added. The
1694
1736
  * consumer proves its native serving MCP process descends from this unique
@@ -4522,6 +4564,19 @@ function loadAuthMetadata(name, kind) {
4522
4564
  }
4523
4565
  return value;
4524
4566
  }
4567
+ function readExecutionAuthMetadata(name) {
4568
+ try {
4569
+ const raw = JSON.parse(fs.readFileSync(authMetadataPath(name), "utf8"));
4570
+ if (!["claude", "codex", "grok", "cursor"].includes(raw?.kind))
4571
+ return null;
4572
+ const meta = loadAuthMetadata(name, raw.kind), launch = executionLaunchSchema.shape.launch_id.safeParse(meta?.launch_id);
4573
+ const native = executionNativeBindingSchema.safeParse(meta?.execution_native);
4574
+ return meta && launch.success && native.success ? { ...meta, launch_id: launch.data, execution_native: native.data } : null;
4575
+ }
4576
+ catch {
4577
+ return null;
4578
+ }
4579
+ }
4525
4580
  /** 一覧の取得失敗を端末の消失へ丸めず、正規PTY一覧で存在を確認する。 */
4526
4581
  function authSessionExists(name) {
4527
4582
  return listSessionsResult().sessions.some(session => session.session_id === name);
@@ -4564,7 +4619,7 @@ export async function authenticateAgent(kind, options) {
4564
4619
  const meta = loadAuthMetadata(options.session_id, kind);
4565
4620
  if (!authSessionExists(options.session_id)) {
4566
4621
  if (meta)
4567
- fs.unlinkSync(authMetadataPath(options.session_id));
4622
+ withExecutionPreparation(options.session_id, () => fs.unlinkSync(authMetadataPath(options.session_id)));
4568
4623
  return result("blocked", null, "認証sessionは既に終了または取消済みです。");
4569
4624
  }
4570
4625
  if (!meta)
@@ -4595,7 +4650,13 @@ export async function authenticateAgent(kind, options) {
4595
4650
  const kept = tmux("set-option", "-t", name, "remain-on-exit", "on");
4596
4651
  if (kept.code !== 0)
4597
4652
  throw new AitermError("AGENT_AUTH_PANE_CONFIG_FAILED: 認証processの終了観測を設定できません。", 2);
4598
- writeJson0600(authMetadataPath(name), meta);
4653
+ if (hasExecutionCreationScope()) {
4654
+ meta.launch_id = randomBytes(16).toString("hex");
4655
+ meta.execution_native = executionCreatedNativeBinding(name);
4656
+ }
4657
+ withExecutionPreparation(name, () => writeJson0600(authMetadataPath(name), meta));
4658
+ if (meta.launch_id && hasExecutionCreationScope())
4659
+ bindExecutionCreatedLaunch({ session_id: name, launch_id: meta.launch_id });
4599
4660
  launchAuthProcess(name, meta);
4600
4661
  }
4601
4662
  catch (error) {
@@ -4643,8 +4704,10 @@ export async function authenticateAgent(kind, options) {
4643
4704
  return result("authenticated", session, null);
4644
4705
  if (meta.phase === "onboarding")
4645
4706
  return result("blocked", session, "公式CLIの初回案内の完了を確認できません。");
4646
- meta.phase = "onboarding";
4647
- writeJson0600(authMetadataPath(session), meta);
4707
+ withExecutionPreparation(session, () => {
4708
+ meta.phase = "onboarding";
4709
+ writeJson0600(authMetadataPath(session), meta);
4710
+ });
4648
4711
  const restarted = tmux("respawn-pane", "-k", "-t", session, resolveWinPaneShell("bash"));
4649
4712
  if (restarted.code !== 0)
4650
4713
  return result("failed", session, "公式CLIの初回案内を起動できません。");
@@ -5039,8 +5102,10 @@ const restoreCursorDefaults = (configPath) => (before) => {
5039
5102
  restoreJsonSetting(configPath, before, CURSOR_DEFAULT_MODEL_KEYS);
5040
5103
  };
5041
5104
  function recordAgentFast(meta, fast) {
5042
- meta.fast = fast;
5043
- writeAgentMetadata(meta);
5105
+ withExecutionPreparation(meta.aiterm_session, () => {
5106
+ meta.fast = fast;
5107
+ writeAgentMetadata(meta);
5108
+ });
5044
5109
  }
5045
5110
  /** 動いている席のfastを切り替える。返りは切り替えた後の状態。席は入力待ちである事(呼び手が確かめる)。 */
5046
5111
  async function switchAgentFast(name, meta, target, verified = false) {
@@ -33,14 +33,14 @@ function routeFor(name, args) {
33
33
  if (!nativeTool.success)
34
34
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
35
35
  const target = launches.has(name) ? args.session_name : name === "pty_open" ? args.name : args.session_id;
36
- // Automatic names are selected inside existing core today. Do not guess the
37
- // future name or act before reserving it at the actual selection boundary.
36
+ // Generic automatic names remain unsupported. Auth start selects its name
37
+ // through the private allocator before this route reserves it.
38
38
  if (target === undefined || target === null)
39
39
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
40
40
  const session = executionLaunchSchema.shape.session_id.safeParse(target);
41
41
  if (!session.success)
42
42
  throw new ExecutionError("EXECUTION_INVALID");
43
- return { tool: nativeTool.data, target: session.data, stage: launches.has(name) ? "launch" : "send" };
43
+ return { tool: nativeTool.data, target: session.data, stage: launches.has(name) || tool === "agent_auth.start" ? "launch" : "send" };
44
44
  }
45
45
  // The SDK signal belongs to this serving RPC, not to a whole CLI turn. Keep
46
46
  // an immediate local gate while the shared metadata lock may be awaiting I/O.
@@ -79,6 +79,19 @@ function requestCancellation(service, request, input) {
79
79
  };
80
80
  }
81
81
  export function createExecutionDispatcher(bindings) {
82
+ const selectAuthName = bindings.selectAuthSessionName, observeAuth = bindings.observeAuthSessionGeneration;
83
+ for (const fn of [selectAuthName, observeAuth])
84
+ if (fn !== undefined && typeof fn !== "function")
85
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
86
+ const authGeneration = (session) => {
87
+ if (!observeAuth)
88
+ return null;
89
+ const value = observeAuth(session);
90
+ if (!value || value.schema !== "aiterm.pty-observe-result.v1" || value.session_id !== session || value.exists !== true)
91
+ return null;
92
+ const launch = executionLaunchSchema.safeParse({ session_id: session, launch_id: value.launch_id });
93
+ return launch.success ? { launch: launch.data, boundary: null } : null;
94
+ };
82
95
  const captureNativeParent = bindings.captureNativeParent;
83
96
  if (captureNativeParent !== undefined && typeof captureNativeParent !== "function")
84
97
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
@@ -129,6 +142,13 @@ export function createExecutionDispatcher(bindings) {
129
142
  executionHostSourceFromMeta(fixedExtra._meta, bindings.initializedClientName());
130
143
  return await body(args, fixedExtra);
131
144
  }
145
+ if (name === "agent_auth" && (args.action !== "status" || args.session_id !== undefined) && !observeAuth)
146
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
147
+ if (name === "agent_auth" && args.action === "start" && args.session_id === undefined) {
148
+ if (!native || !selectAuthName)
149
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
150
+ args.session_id = executionLaunchSchema.shape.session_id.parse(selectAuthName());
151
+ }
132
152
  const route = routeFor(name, args);
133
153
  if (!route)
134
154
  return await body(args, fixedExtra);
@@ -145,7 +165,12 @@ export function createExecutionDispatcher(bindings) {
145
165
  const hostClose = client === "bellteam" && route.tool === "pty_close" && !supplied;
146
166
  const hostLaunch = client === "bellteam" && route.tool === "agent_launch" && !supplied
147
167
  && args.prompt == null && args.throughline_source_session === undefined;
148
- const hostManagement = hostClose || hostLaunch;
168
+ const hostConfigure = client === "bellteam" && route.tool === "agent_configure" && !supplied;
169
+ const authOperation = name === "agent_auth";
170
+ const authInput = (route.tool === "pty_send" || route.tool === "pty_key") && !supplied
171
+ && client === "bellteam" ? authGeneration(route.target) : null;
172
+ const hostAuth = client === "bellteam" && !supplied && authOperation;
173
+ const hostManagement = hostClose || hostLaunch || hostConfigure || hostAuth || !!authInput;
149
174
  if (args.remote !== undefined && args.remote !== null)
150
175
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
151
176
  let reference = supplied;
@@ -195,8 +220,16 @@ export function createExecutionDispatcher(bindings) {
195
220
  if ((client === "bellteam" && !hostManagement) || !directRequest)
196
221
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
197
222
  const current = await observe(route.target);
223
+ if (authOperation && route.tool !== "agent_auth.start") {
224
+ // A missing auth PTY returns the public v1 failed/already-cancelled
225
+ // receipt. There is no native effect or replacement creation.
226
+ if (!current.point)
227
+ route.stage = "launch";
228
+ }
198
229
  if (route.stage === "send" && !current.point)
199
230
  throw new ExecutionError("EXECUTION_STALE_GENERATION");
231
+ if (authInput && !same(authInput, current.point))
232
+ throw new ExecutionError("EXECUTION_STALE_GENERATION");
200
233
  const request = directRequest;
201
234
  cancellation?.assert();
202
235
  await bindings.service.execute({ action: "register", source_session_id: source?.launch.session_id ?? directSeat,
@@ -230,7 +263,7 @@ export function createExecutionDispatcher(bindings) {
230
263
  // must not turn a valid accepted action into permission to resend.
231
264
  let target = null;
232
265
  try {
233
- if (route.tool === "pty_close")
266
+ if (route.tool === "pty_close" || route.tool === "agent_auth.cancel")
234
267
  target = expected ? { launch: expected, boundary: null } : null;
235
268
  else {
236
269
  const current = await observe(route.target);
@@ -0,0 +1,9 @@
1
+ // Private read-only evidence from the serving native scope, never client claims.
2
+ import { z } from "zod";
3
+ import { executionLaunchSchema } from "./execution-contract.js";
4
+ import { executionNativeBindingSchema } from "./execution-close.js";
5
+ export const createdLaunchObservationSchema = z.object({
6
+ target: executionLaunchSchema, native: executionNativeBindingSchema,
7
+ initial_prompt: z.literal("none"), initial_prompt_delivery: z.null(),
8
+ harness_alive: z.literal(true), ready: z.literal(true),
9
+ }).strict();
@@ -35,6 +35,9 @@ export function createNativeExecutionServer(input) {
35
35
  ?? provider.cursorParentFromRequest(profile, client, { hookRoot: path.join(expectedScope.state_root, "cursor-parent-hooks") }),
36
36
  observeDone: core.observeAgentDone,
37
37
  observeNativeSessions: core.observeExecutionNativeCreationScope,
38
+ selectAuthSessionName: core.selectExecutionAuthSessionName,
39
+ observeAuthSessionGeneration: core.observeExecutionAuthSessionGeneration,
40
+ observeCreatedLaunchSync: core.observeExecutionCreatedLaunch,
38
41
  closeExpectedSession: core.closeExpectedAgentSession, // explicit management only, never stop fallback
39
42
  observeInterruptionSync: core.observeExpectedAgentRunInterruption,
40
43
  selectInterruptionSync: core.selectExpectedAgentRunInterruption,
@@ -1,7 +1,7 @@
1
1
  // Private same-scope MCP parent producer. No public/index/profile mount. Queue
2
2
  // aliases lacking current independent native proof remain unsupported.
3
3
  import path from "node:path";
4
- import { observeAgentDone, observeExecutionNativeCreationScope, closeExpectedAgentSession } from "./core.js";
4
+ import { observeAgentDone, selectExecutionAuthSessionName, observeExecutionAuthSessionGeneration, observeExecutionNativeCreationScope, closeExpectedAgentSession, observeExecutionCreatedLaunch } from "./core.js";
5
5
  import { codexParentFromRequest } from "./codex-parent-receiver.js";
6
6
  import { claudeParentFromRequest } from "./claude-parent-receiver.js";
7
7
  import { cursorParentFromRequest } from "./cursor-parent-receiver.js";
@@ -30,13 +30,18 @@ const localProducerRuntime = {
30
30
  ?? cursorParentFromRequest(client, { hookRoot: path.join(stateRoot(), "cursor-parent-hooks") }),
31
31
  observeDone: observeAgentDone,
32
32
  observeNativeSessions: observeExecutionNativeCreationScope,
33
+ selectAuthSessionName: selectExecutionAuthSessionName,
34
+ observeAuthSessionGeneration: observeExecutionAuthSessionGeneration,
35
+ observeCreatedLaunchSync: observeExecutionCreatedLaunch,
33
36
  closeExpectedSession: closeExpectedAgentSession,
34
37
  };
35
38
  export function createExecutionParentProducer(input) {
36
39
  const runtime = input.runtime ?? localProducerRuntime;
37
- for (const name of ["observeInterruptionSync", "selectInterruptionSync", "interruptKeySync", "observeOriginOwner"])
40
+ for (const name of ["selectAuthSessionName", "observeAuthSessionGeneration", "observeInterruptionSync", "selectInterruptionSync", "interruptKeySync", "observeOriginOwner", "observeCreatedLaunchSync"])
38
41
  if (runtime[name] !== undefined && typeof runtime[name] !== "function")
39
42
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
43
+ const selectAuthName = runtime.selectAuthSessionName?.bind(runtime), observeAuth = runtime.observeAuthSessionGeneration?.bind(runtime);
44
+ const createdLaunch = runtime.observeCreatedLaunchSync?.bind(runtime);
40
45
  const interruption = runtime.observeInterruptionSync?.bind(runtime);
41
46
  const selectInterruption = runtime.selectInterruptionSync?.bind(runtime), interruptKey = runtime.interruptKeySync?.bind(runtime);
42
47
  const observeActors = runtime.observeActors?.bind(runtime);
@@ -68,6 +73,10 @@ export function createExecutionParentProducer(input) {
68
73
  } } : {}),
69
74
  ...(interruptKey ? { interruptKeySync: (point) => { assertScope(); return interruptKey(point); } } : {}),
70
75
  observeSession: observe, observeSessionSync: observe,
76
+ ...(createdLaunch ? { observeCreatedLaunchSync: (target) => {
77
+ assertScope();
78
+ return createdLaunch(target);
79
+ } } : {}),
71
80
  ...(typeof runtime.observeNativeSessions === "function" ? { observeNativeSessionsSync: () => {
72
81
  assertScope();
73
82
  return runtime.observeNativeSessions();
@@ -115,7 +124,10 @@ export function createExecutionParentProducer(input) {
115
124
  requireExecutionEffectScope(true);
116
125
  return { execution, parent, native, service, store, scope: structuredClone(scope), worker,
117
126
  inventory: () => store.inventory(typeof runtime.observeActors === "function" ? pids => { assertScope(); return runtime.observeActors(pids); } : undefined),
118
- startup: Object.freeze({ actor: Object.freeze({ ...actor }), sourceEnvironment, assertScope, observeSessionGeneration: observe,
127
+ startup: Object.freeze({
128
+ ...(selectAuthName ? { selectAuthSessionName: () => { assertScope(); return selectAuthName(); } } : {}),
129
+ ...(observeAuth ? { observeAuthSessionGeneration: (session) => { assertScope(); return observeAuth(session); } } : {}),
130
+ actor: Object.freeze({ ...actor }), sourceEnvironment, assertScope, observeSessionGeneration: observe,
119
131
  captureNativeParent: () => {
120
132
  assertScope();
121
133
  const fixed = sourceEvidence.captureIngress();
@@ -124,6 +136,7 @@ export function createExecutionParentProducer(input) {
124
136
  if (fixed?.proof.endpoint.vendor === "claude")
125
137
  producerEndpoint.verifyIngress(fixed.endpoint, fixed.proof);
126
138
  return fixed;
127
- } }),
139
+ }
140
+ }),
128
141
  relation_coverage: "incomplete" };
129
142
  }
@@ -12,7 +12,7 @@ function snapshotRuntime(input) {
12
12
  for (const name of functions)
13
13
  if (typeof input[name] !== "function")
14
14
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
15
- const optional = ["observeNativeSessions", "closeExpectedSession", "observeActors", "observeInterruptionSync", "selectInterruptionSync", "interruptKeySync", "observeOriginOwner"];
15
+ const optional = ["selectAuthSessionName", "observeAuthSessionGeneration", "observeNativeSessions", "closeExpectedSession", "observeActors", "observeInterruptionSync", "selectInterruptionSync", "interruptKeySync", "observeOriginOwner", "observeCreatedLaunchSync"];
16
16
  for (const name of optional)
17
17
  if (input[name] !== undefined && typeof input[name] !== "function")
18
18
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
@@ -70,7 +70,9 @@ export function createExecutionParticipant(input) {
70
70
  const server = createAitermServer({ execution: server => {
71
71
  assertScope();
72
72
  dispatcher = createExecutionDispatcher({ service: producer.service, nativeIdentity: actor, sourceEnvironment,
73
- initializedClientName: () => server.server.getClientVersion()?.name, observeSessionGeneration, assertParticipant: assertScope, captureNativeParent });
73
+ initializedClientName: () => server.server.getClientVersion()?.name, observeSessionGeneration, assertParticipant: assertScope, captureNativeParent,
74
+ ...(producer.startup.selectAuthSessionName ? { selectAuthSessionName: producer.startup.selectAuthSessionName } : {}),
75
+ ...(producer.startup.observeAuthSessionGeneration ? { observeAuthSessionGeneration: producer.startup.observeAuthSessionGeneration } : {}) });
74
76
  return { dispatcher, parentExecution: producer.execution, parentDependencies, parentStateRoot: producer.scope.state_root };
75
77
  } });
76
78
  producer.store.enroll(["mcp"]);
@@ -45,7 +45,7 @@ const waitSchema = z.object({
45
45
  });
46
46
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
47
47
  export function createExecutionRuntime(bindings) {
48
- for (const name of ["observeInterruptionSync", "selectInterruptionSync", "interruptKeySync"])
48
+ for (const name of ["observeCreatedLaunchSync", "observeInterruptionSync", "selectInterruptionSync", "interruptKeySync"])
49
49
  if (bindings[name] !== undefined && typeof bindings[name] !== "function")
50
50
  throw new ExecutionError("EXECUTION_UNSUPPORTED");
51
51
  const interruption = bindings.observeInterruptionSync;
@@ -72,7 +72,9 @@ export function createExecutionRuntime(bindings) {
72
72
  return { state: "unknown" };
73
73
  }
74
74
  };
75
+ const created = bindings.observeCreatedLaunchSync;
75
76
  return {
77
+ ...(created ? { observeCreatedLaunchSync: (target) => created(executionLaunchSchema.parse(target)) } : {}),
76
78
  ...(interruption ? { interruptionSync: (point) => interruption(executionPointSchema.parse(point)) } : {}),
77
79
  ...(selectInterruption ? { selectInterruptionSync: (expected, candidates) => selectInterruption(executionLaunchSchema.parse(expected), z.array(executionPointSchema).parse(candidates ?? [])) } : {}),
78
80
  ...(interruptKey ? { interruptKeySync: (point) => interruptKey(executionPointSchema.parse(point)) } : {}),
@@ -8,6 +8,7 @@ import { nativeInterruptionObservationSchema, interruptionIdentity, interruption
8
8
  import { interruptionKeyReference } from "./execution-interruption.js";
9
9
  import { isDeepStrictEqual } from "node:util";
10
10
  import { executionActorSchema } from "./execution-inventory.js";
11
+ import { createdLaunchObservationSchema } from "./execution-launch-recovery.js";
11
12
  import { executionCheckpoint } from "./execution-checkpoint.js";
12
13
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
13
14
  const key = (value) => JSON.stringify(value);
@@ -544,6 +545,8 @@ export class ExecutionService {
544
545
  return this.register(args, context);
545
546
  if (args.action === "status")
546
547
  return this.store.transaction(state => view(state, find(state, args.request)), { readOnly: true });
548
+ if (args.action === "reconcile_launch")
549
+ return this.reconcileLaunch(args);
547
550
  return this.finish(args);
548
551
  }
549
552
  async admitNative(body) {
@@ -878,6 +881,9 @@ export class ExecutionService {
878
881
  try {
879
882
  await this.store.transaction(state => {
880
883
  const record = find(state, request);
884
+ const owned = state.slots.find(slot => same(slot.request, request));
885
+ if (!owned || owned.stage !== stage || record.operations[stage] !== "issued")
886
+ return;
881
887
  record.operations[stage] = certain ? "not_sent" : "unknown";
882
888
  if (certain) {
883
889
  state.slots = state.slots.filter(slot => !same(slot.request, request));
@@ -909,7 +915,7 @@ export class ExecutionService {
909
915
  || (expected !== null && targetPoint.launch.launch_id !== expected))
910
916
  throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
911
917
  const current = await this.runtime.observe(slot.session_id);
912
- const closing = request.kind === "mcp" && request.tool === "pty_close";
918
+ const closing = request.kind === "mcp" && ["pty_close", "agent_auth.cancel"].includes(request.tool);
913
919
  if (closing ? current.state !== "absent" : current.state !== "present" || !same(current.launch, targetPoint.launch))
914
920
  throw new ExecutionError("EXECUTION_UNCONFIRMED", "possible");
915
921
  record.operations[stage] = "accepted";
@@ -940,6 +946,9 @@ export class ExecutionService {
940
946
  try {
941
947
  await this.store.transaction(state => {
942
948
  const record = find(state, request);
949
+ const owned = state.slots.find(slot => same(slot.request, request));
950
+ if (!owned || owned.stage !== stage || record.operations[stage] !== "issued")
951
+ return;
943
952
  record.operations[stage] = "unknown";
944
953
  record.relation.phase = "submitted_unknown";
945
954
  record.relation.completion = "unknown";
@@ -953,6 +962,61 @@ export class ExecutionService {
953
962
  });
954
963
  return { value: outcome.value, record };
955
964
  }
965
+ /** Explicit present-generation reconciliation. It never replays the original
966
+ * launch or input, and does not claim to recover the lost RPC receipt. */
967
+ async reconcileLaunch(args) {
968
+ return this.store.transaction(state => {
969
+ const record = find(state, args.request), relation = record.relation;
970
+ if (record.operations.launch === "accepted" && same(relation.target?.launch, args.target)) {
971
+ // A saved accepted receipt is historical. Before a caller can use an
972
+ // empty launch to resume input, prove that this generation is still
973
+ // present and no later operation owns the same session slot.
974
+ if (record.operations.send === "not_issued") {
975
+ if (record.stopping || stopped(state, args.target) || stopped(state, relation.source?.launch)
976
+ || interruptionBlocksRequest(state, args.request))
977
+ throw new ExecutionError("EXECUTION_STOPPING");
978
+ if (state.slots.some(slot => slot.session_id === args.target.session_id) || openParentEffect(state, args.request))
979
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
980
+ if (!this.runtime.observeCreatedLaunchSync)
981
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
982
+ const parsed = createdLaunchObservationSchema.safeParse(this.runtime.observeCreatedLaunchSync(args.target));
983
+ if (!parsed.success || !same(parsed.data.target, args.target))
984
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
985
+ }
986
+ return view(state, record);
987
+ }
988
+ if (record.operations.launch === "issued")
989
+ throw new ExecutionError("EXECUTION_BUSY");
990
+ if (record.stopping || stopped(state, args.target) || stopped(state, relation.source?.launch)
991
+ || interruptionBlocksRequest(state, args.request))
992
+ throw new ExecutionError("EXECUTION_STOPPING");
993
+ const slots = state.slots.filter(slot => slot.session_id === args.target.session_id);
994
+ const slot = slots[0];
995
+ if (record.operations.launch !== "unknown" || record.operations.send !== "not_issued"
996
+ || relation.phase !== "submitted_unknown" || relation.completion !== "unknown" || relation.lifecycle !== "tracking"
997
+ || record.reservation.target.session_id !== args.target.session_id
998
+ || relation.target?.boundary != null || (relation.target && !same(relation.target.launch, args.target))
999
+ || (record.reservation.target.expected_launch_id !== null && record.reservation.target.expected_launch_id !== args.target.launch_id)
1000
+ || slots.length !== 1 || !slot || !same(slot.request, args.request) || slot.stage !== "launch" || slot.state !== "unknown"
1001
+ || !slot.created_native_id || !slot.created_server
1002
+ || (slot.expected_launch_id !== null && slot.expected_launch_id !== args.target.launch_id)
1003
+ || openParentEffect(state, args.request))
1004
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
1005
+ if (!this.runtime.observeCreatedLaunchSync)
1006
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
1007
+ const parsed = createdLaunchObservationSchema.safeParse(this.runtime.observeCreatedLaunchSync(args.target));
1008
+ if (!parsed.success || !same(parsed.data.target, args.target)
1009
+ || parsed.data.native.native_id !== slot.created_native_id
1010
+ || !isDeepStrictEqual(parsed.data.native.server, slot.created_server))
1011
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
1012
+ record.operations.launch = "accepted";
1013
+ relation.target = { launch: args.target, boundary: null };
1014
+ relation.phase = "invoked";
1015
+ relation.completion = "open";
1016
+ state.slots = state.slots.filter(value => value !== slot);
1017
+ return view(state, record);
1018
+ });
1019
+ }
956
1020
  async finish(args) {
957
1021
  return this.store.transaction(async (state) => {
958
1022
  const record = find(state, args.request);
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { executionReservationSchema, executionRequestSchema, executionPointSchema, cascadeInterruptRequestSchema, cascadeInterruptResultObjectSchema } from "./execution-contract.js";
2
+ import { executionLaunchSchema, executionReservationSchema, executionRequestSchema, executionPointSchema, cascadeInterruptRequestSchema, cascadeInterruptResultObjectSchema } from "./execution-contract.js";
3
3
  import { executionActionSchema, executionFinishSchema, executionRecordSchema, executionBindingSchema, executionRootsRequestSchema, executionRootsResultObjectSchema, executionRefFromMeta, executionErrorResult, ExecutionError, EXECUTION_BINDING_META_KEY, EXECUTION_ERROR_META_KEY, EXECUTION_SOURCE_META_KEY } from "./execution-wire.js";
4
4
  import { withExecutionEffectScope, bindExecutionManagementCloseTarget } from "./execution-effect.js";
5
5
  import { immediateStopInputSchema, immediateStopResultSchema } from "./execution-immediate-stop.js";
@@ -67,12 +67,12 @@ export function registerExecutionTools(server, service, options = {}) {
67
67
  const handlers = createExecutionToolHandlers(service, { ...options,
68
68
  ...(registration ? { sourceContext: registration.sourceContext } : {}) });
69
69
  server.registerTool("agent_execution", {
70
- description: "既存の実配送/実行参照を送信前に登録、照会、対応終了証拠で更新する。本文や再送queueは保存しない。",
70
+ description: "既存の実配送/実行参照を登録・照会・終了証拠で更新し、結果不明の空起動を同世代へ照合する。本文や再送queueは保存しない。",
71
71
  // MCP accepts a raw object shape; the handler enforces the stricter action
72
72
  // union, including required fields and forbidden fields for each action.
73
- inputSchema: { action: z.enum(["register", "status", "finish"]),
73
+ inputSchema: { action: z.enum(["register", "status", "finish", "reconcile_launch"]),
74
74
  reservation: executionReservationSchema.optional(), source_session_id: z.string().nullable().optional(),
75
- request: executionRequestSchema.optional(), evidence: executionFinishSchema.shape.evidence.optional(),
75
+ request: executionRequestSchema.optional(), target: executionLaunchSchema.optional(), evidence: executionFinishSchema.shape.evidence.optional(),
76
76
  remote: z.never().optional() },
77
77
  outputSchema: executionRecordSchema.shape,
78
78
  }, async (args, extra) => handlers.agent_execution(args, extra));
@@ -43,7 +43,10 @@ export const executionFinishSchema = z.object({
43
43
  z.object({ kind: z.literal("interrupted"), target: executionLaunchSchema }).strict(),
44
44
  ]),
45
45
  }).strict();
46
- export const executionActionSchema = z.union([executionRegisterSchema, executionStatusSchema, executionFinishSchema]);
46
+ export const executionReconcileLaunchSchema = z.object({
47
+ action: z.literal("reconcile_launch"), request: executionRequestSchema, target: executionLaunchSchema,
48
+ }).strict();
49
+ export const executionActionSchema = z.union([executionRegisterSchema, executionStatusSchema, executionFinishSchema, executionReconcileLaunchSchema]);
47
50
  export const executionRefMetaSchema = z.object({
48
51
  schema: z.literal("aiterm.execution-ref.v1"), request: executionRequestSchema,
49
52
  }).strict();
package/docs/DESIGN.md CHANGED
@@ -37,13 +37,16 @@ incomingのslotを解放・書換・成功扱いせず、unknown・管理操作
37
37
  incomingの完了待ちをoutgoingへ課して相互送信を循環待ちにしない(ADR 0127)。
38
38
  BellTeamのpromptなし明示管理launchは、管理close同様にserving process/request identityへ結合する。
39
39
  promptやThroughline読出しを含む未登録送信、auto-name、remoteはこの管理例外へ含めない。
40
+ BellTeamのrefなしagent_configureも同じnative process/request identityの管理RPCとして受ける。
41
+ model/effort/fastの各stageは同じ既存世代・slot・source・停止のgateを通り、途中で変われば続きの打鍵を拒否する。
42
+ 発行後の途中失敗はpossible/unknownを保持する。CLI結果待ち後のfast metadata書込も同じ準備受付で再検査する(ADR 0129)。
40
43
  新launchは、既存POSIX serverのnative owner、またはserver未作成の完全な空観測を受付時と発行直前に確認する。
41
44
  冷間時は最初のnew-session自身でserverを作り、その実receiptのnative IDとserver PIDを、作成後の安定したnative一覧とprocess開始識別子で照合する。
42
45
  既存server時も冷間時も、作成したsessionのnative ID・server owner・core内部のlaunch世代を同じ要求slotへ段階結合する。
43
46
  物理shell準備はnative IDへ固定し、未結合の同名世代を採用しない。冷間起動のための補助sessionや再送は作らない。
44
47
  作成時のnative ID/server ownerをmanaged metadataへ保存し、private runtimeの世代読取と期待closeでも再照合する。
45
48
  古いmetadataの欠測を現観測で補わず、既存席の自動登録は行わない。
46
- 証拠の無い新server起動、Windows固有のserver作成、auto-name/通常PTY/auth等はまだunsupported。
49
+ 証拠の無い新server起動、Windows固有のserver作成、一般のauto-name/通常PTY等はまだunsupported。認証専用PTYはADR 0130の限定経路で受け付ける。
47
50
  実18 toolsのhandlerを作るconstructorは、private dispatcher/親execution callbackを明示注入して隔離SDKで検証できる。
48
51
  通常のstdio mainは注入せず、全参加process/全core作用口・native fenceが揃うまで公開有効化しない。
49
52
  private participant factoryはproducer/service/dispatcher/同process用worker profileを同じscopeとnative actorで組む。
@@ -405,6 +408,14 @@ shell、接続先、各harnessの公式CLIが所有する。
405
408
  ## Model catalog
406
409
 
407
410
  `agent_auth`の共通進行はcore、公式command・status・認証画面の解釈は`src/harnesses/`が所有する。CLIの認証processを通常HOMEのPTYへ一度だけ起動し、`remain-on-exit`と`pane_dead_status`で終了を観測する。credentialを読み取る状態管理や独自OAuthは持たず、Aitermが保存するのは認証sessionのharness・実行file・cwd・phase・引き継いだenvの名前だけである。取消と通常のPTY closeでこの相関記録を削除する。
411
+ native明示入口のauth startは、既存SDK RPCのprocess/start/request identityへ結合し、session省略時は一意なPTY名だけを選んで先に予約する。
412
+ 認証済みno-opはPTY/serverを作らず元のv1/session_id:nullを返す。作成時は実new-session receiptからauth metadataへ
413
+ native/server stampと新しいauth世代を保存・結合する。通常agent metadataや本文delivery refを捏造しない。
414
+ 既存authのstatus/onboarding・取消は同じ世代を各作用前に再検査する。消失済みはnative作用なしで既存v1 receiptを返す。
415
+ BellTeamのrefなしpty_send/pty_keyは、この作成proofを保持するauth PTYにだけ許可し、通常Botの未登録本文には広げない。
416
+ 初回案内のrespawnも同じsession世代を保ち、phase保存は準備受付を通す。旧auth metadataの欠測を現nativeから補わず、
417
+ 不明な入力・世代・未返却RPCのslotをstatusだけで解除しない。credentialの保存・操作は公式CLIが所有する。
418
+
408
419
 
409
420
  PTYが消失した場合は、相関記録の有無にかかわらず`status`が`failed`、`cancel`が既に終了・取消済みを示す`blocked`を返し、どちらも`session_id:null`となる。保存したsession IDを解除して`start`で再開始できる。生存中の通常PTYやharness不一致、記録の破損・読取り失敗はエラーを返す。
410
421
 
@@ -442,6 +453,15 @@ unknown send枠を保持したまま既存core closeへ進み、native close成
442
453
  旧callbackは未知/消失した元slotで拒否され、同IDの新launchへnative作用を発行できない。
443
454
  issued、管理close自体のunknown、親作用のslot、世代/native/serverの不明・交代は対象外。
444
455
  既存の送信・待機lockの拒否も維持し、他席やissuerの停止を復旧条件へ追加しない。
456
+ 空起動の結果不明は明示 `agent_execution/reconcile_launch` で既存世代へ結合できる(ADR 0128)。
457
+ 対象は返却済みunknown launch、send:not_issued、元native/server stamp付きの単独slotだけ。
458
+ 同じmetadata transaction内のprivate同期primitiveで現在のnative/managed世代、生のinitial_prompt:none・初期配送なし、
459
+ harness生存とidleを照合する。既存recordのlaunchをaccepted、targetを当該世代へ結合し、該当slotだけを外す。
460
+ これは現在世代の明示採用で、失われたRPC receiptの復元ではない。本文・初期prompt・claim・nativeには作用しない。
461
+ 同じaccepted targetへの再照合もsend:not_issuedなら現在のmanaged世代・生の未入力状態・入力待ちを再確認し、
462
+ 同じsessionへの競合slotや親の未確定作用があれば拒否する。保存済みacceptedを後の世代の不明解除の証拠にしない。
463
+ issued、入力済み・不明、停止、別slot、世代/native/serverの不一致は拒否。後続sendは元source世代を含め通常受付を通る。
464
+ 遅着した元callbackはまだ所有するissued stage/slotがある時だけunknownを書け、照合後や別stageを上書きしない。
445
465
  native handlerのisErrorは元の本文を保持し、`aiterm/execution_error`のcode/effectを併記する。
446
466
  native作用後の不明はpossibleのままで、原因説明を再送可否や成功receiptへ変換しない。
447
467
  agent sessionへの送信1本を守る`<name>.agent-send.lock`は持つ時間が長いので、持ち主が終了した残骸を次の送信が片付ける。消す役を`<name>.agent-send.lock.reap`の排他作成で1つに絞り、
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.61.6",
3
+ "version": "0.61.8",
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": [