aiterm-mcp 0.61.5 → 0.61.7

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.7] - 2026-10-23
11
+
12
+ ### Fixed
13
+
14
+ - 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.
15
+ - 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.
16
+
17
+ ### Added
18
+
19
+ - 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.
20
+
21
+ ## [0.61.6] - 2026-10-22
22
+
23
+ ### Fixed
24
+
25
+ - Agent startup waits asynchronously for the existing metadata mutex before checking pane ownership, restoring its terminal mode, or accepting startup-menu keys. A short concurrent writer after promptless native creation no longer turns an otherwise valid launch into `unknown`. Stops, uncertain slots, generation changes and the existing lock-acquisition deadline still refuse further effects; no launch or input is replayed.
26
+
10
27
  ## [0.61.5] - 2026-10-22
11
28
 
12
29
  ### Fixed
@@ -2736,7 +2753,9 @@ prototype (preserved under `prototype/python/` as the porting source and referen
2736
2753
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
2737
2754
  provenance.
2738
2755
 
2739
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.5...HEAD
2756
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.7...HEAD
2757
+ [0.61.7]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.6...v0.61.7
2758
+ [0.61.6]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.5...v0.61.6
2740
2759
  [0.61.5]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.4...v0.61.5
2741
2760
  [0.61.4]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.3...v0.61.4
2742
2761
  [0.61.3]: https://github.com/kitepon/aiterm-mcp/compare/v0.61.2...v0.61.3
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.5** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.61.7** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
213
213
 
214
214
  ### 更新と巻き戻し
215
215
 
@@ -559,6 +559,7 @@ MCPの本体を初めて使う時まで起こさない中継の後ろにAiterm
559
559
 
560
560
  `agent_launch({ harness, cwd, trust_project: true })`はpromptなしでも既知のworkspace・project hooks・MCP初期同意を
561
561
  進め、入力受付とharness生存を確認して`startup.status="ready"`を返す。指定なしのpromptなし起動は`not_checked`。
562
+ 起動準備は競合するmetadata保存が終わるのを待って続行する。端末回復と起動メニューの各キーでは世代・停止状態を再確認し、native起動が未確認になった場合は自動再実行せず診断用に保持する。
562
563
  Claude Code初回起動の文字表示テーマ選択では、画面で選択済みの項目を確定して起動を続ける。
563
564
  続いてログイン方式の選択が出た場合は`vendor_onboarding_required`を返す。公式の対話型Claude Code CLIで初回設定を完了する。
564
565
  入力受付の待ちは30秒。混んだ端末でagentの画面がまだ描かれていない間に切れた時だけ、起動から50秒まで待つ。
@@ -733,6 +734,13 @@ agent_launch({ "harness": "codex-cli", "remote": { "host": "rabbit" }, "cwd": "/
733
734
 
734
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が不明な席は引き続き拒否する。
735
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
+
736
744
  ## 人が覗く
737
745
 
738
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.5** · 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.7** · 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
 
@@ -600,6 +600,7 @@ Owner start times are checked through `/proc` on Linux; elsewhere an existing PI
600
600
 
601
601
  `agent_launch({ harness, cwd, trust_project: true })` completes known workspace, project-hook, and project-MCP startup
602
602
  consent even without a prompt, then verifies input readiness and harness liveness before returning `startup.status="ready"`.
603
+ Startup checks wait for concurrent metadata writers before continuing. Each terminal recovery or startup-menu key still checks the current generation and stop state; an uncertain native launch remains available for status inspection without automatic replay.
603
604
  For Claude Code's first-run text-style menu, it confirms the item already selected on screen before continuing startup.
604
605
  If the CLI then requests an account login method, the launch reports `vendor_onboarding_required`; complete that choice in the official interactive Claude Code CLI.
605
606
  The input-readiness wait is 30 seconds. Only when it expires while the agent has not drawn anything yet (a busy host), the launch
@@ -795,9 +796,16 @@ Each `pty_send` accepts at most 64 KiB of UTF-8 text. Sends to the same session
795
796
 
796
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.
797
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. 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
+
798
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.
799
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.
800
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
+
801
809
  ## Requirements
802
810
 
803
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";
@@ -209,7 +209,10 @@ function paneTtyOf(name) {
209
209
  * agent が tty 上に実在しない時は触らない(ready gate が入力受付不能として止める)。
210
210
  */
211
211
  export async function ensureAgentOwnsPaneInput(name, kind) {
212
- assertExecutionContext(name);
212
+ // Startup resumes after native creation/readiness awaits. A short metadata
213
+ // writer is not a reason to abandon that already created launch. Wait for
214
+ // admission, then run the unchanged source/target/generation checks.
215
+ await withExecutionNativeAdmission(name, () => assertExecutionContext(name));
213
216
  if (isWin)
214
217
  return [];
215
218
  const tty = paneTtyOf(name);
@@ -225,9 +228,11 @@ export async function ensureAgentOwnsPaneInput(name, kind) {
225
228
  return [];
226
229
  if (state.agentPresent && !state.agentForeground) {
227
230
  const wasStopped = state.agentStopped;
228
- tmux("send-keys", "-t", name, "C-u");
229
- tmux("send-keys", "-l", "-t", name, "fg");
230
- tmux("send-keys", "-t", name, "Enter");
231
+ await withExecutionNativeAdmission(name, () => {
232
+ tmux("send-keys", "-t", name, "C-u");
233
+ tmux("send-keys", "-l", "-t", name, "fg");
234
+ tmux("send-keys", "-t", name, "Enter");
235
+ });
231
236
  const deadline = performance.now() + AGENT_FOREGROUND_RECOVERY_TIMEOUT_MS;
232
237
  for (;;) {
233
238
  await sleep(AGENT_FOREGROUND_RECOVERY_POLL_MS);
@@ -250,11 +255,13 @@ export async function ensureAgentOwnsPaneInput(name, kind) {
250
255
  // stty is a native mutation outside tmux's command gate. Recheck the
251
256
  // captured device and the shared request generation/fence immediately
252
257
  // before dispatch; a replaced pane must not inherit the old tty repair.
253
- beforeExecutionEffect(name, () => {
254
- if (paneTtyOf(name) !== tty)
255
- throw new ExecutionError("EXECUTION_STALE_GENERATION");
258
+ const set = await withExecutionNativeAdmission(name, () => {
259
+ beforeExecutionEffect(name, () => {
260
+ if (paneTtyOf(name) !== tty)
261
+ throw new ExecutionError("EXECUTION_STALE_GENERATION");
262
+ });
263
+ return spawnSync("/bin/stty", [ttyFlag, tty, "raw", "-echo"], { encoding: "utf8", timeout: 5000, env: psEnv });
256
264
  });
257
- const set = spawnSync("/bin/stty", [ttyFlag, tty, "raw", "-echo"], { encoding: "utf8", timeout: 5000, env: psEnv });
258
265
  if (set.status !== 0) {
259
266
  throw new AitermError(`AGENT_TTY_COOKED session=${name}\n` +
260
267
  `tty が icanon/echo のままで raw へ戻せません: ${(set.stderr ?? "").trim() || `code=${set.status}`}。送信していません。`, 2);
@@ -1671,17 +1678,59 @@ export function observeExecutionSessionGeneration(name) {
1671
1678
  const native = before.sessions.find(value => value.session_id === name);
1672
1679
  if (!native)
1673
1680
  return result(false, null);
1674
- const meta = tryLoadAgentMetadata(name), proof = meta?.execution_native;
1681
+ const meta = tryLoadAgentMetadata(name) ?? readExecutionAuthMetadata(name), proof = meta?.execution_native;
1675
1682
  if (!meta || !proof || !before.server || proof.native_id !== native.native_id
1676
1683
  || !isDeepStrictEqual(proof.server, before.server))
1677
1684
  return result(true, null);
1678
- const after = observeExecutionNativeCreationScope(), last = tryLoadAgentMetadata(name);
1685
+ const after = observeExecutionNativeCreationScope(), last = tryLoadAgentMetadata(name) ?? readExecutionAuthMetadata(name);
1679
1686
  if (after.state !== "known" || !isDeepStrictEqual(after.server, proof.server)
1680
1687
  || after.sessions.find(value => value.session_id === name)?.native_id !== proof.native_id
1681
- || last?.launch_id !== meta.launch_id || !isDeepStrictEqual(last.execution_native, proof))
1688
+ || last?.launch_id !== meta.launch_id || !isDeepStrictEqual(last?.execution_native, proof))
1682
1689
  return result(true, null);
1683
1690
  return result(true, meta.launch_id);
1684
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
+ }
1685
1734
  /** Private source-provenance snapshot. It deliberately omits screen, argv,
1686
1735
  * environment and cached vendor_session_id. No public tool is added. The
1687
1736
  * consumer proves its native serving MCP process descends from this unique
@@ -4230,7 +4279,7 @@ async function prepareAgentInput(name, meta, options) {
4230
4279
  handled.add(action.kind);
4231
4280
  const keys = action.selected ? action.keys.slice(0, -1) : action.keys;
4232
4281
  for (const key of keys) {
4233
- sendKey(name, key, { preserveAgentOperation: true });
4282
+ await withExecutionNativeAdmission(name, () => sendKey(name, key, { preserveAgentOperation: true }));
4234
4283
  await sleep(AGENT_SUBMIT_DELAY_MS);
4235
4284
  }
4236
4285
  if (action.selected) {
@@ -4238,14 +4287,14 @@ async function prepareAgentInput(name, meta, options) {
4238
4287
  let moved = await waitStartupSelection(name, action.selected);
4239
4288
  if (!moved && keys.length > 0) {
4240
4289
  for (const key of keys) {
4241
- sendKey(name, key, { preserveAgentOperation: true });
4290
+ await withExecutionNativeAdmission(name, () => sendKey(name, key, { preserveAgentOperation: true }));
4242
4291
  await sleep(AGENT_SUBMIT_DELAY_MS);
4243
4292
  }
4244
4293
  moved = await waitStartupSelection(name, action.selected);
4245
4294
  }
4246
4295
  if (!moved)
4247
4296
  break;
4248
- sendKey(name, action.keys.at(-1), { preserveAgentOperation: true });
4297
+ await withExecutionNativeAdmission(name, () => sendKey(name, action.keys.at(-1), { preserveAgentOperation: true }));
4249
4298
  await sleep(AGENT_SUBMIT_DELAY_MS);
4250
4299
  }
4251
4300
  ready = await waitAgentTuiReady(name, meta, options.ready_timeout ?? AGENT_TUI_READY_TIMEOUT_MS);
@@ -4515,6 +4564,19 @@ function loadAuthMetadata(name, kind) {
4515
4564
  }
4516
4565
  return value;
4517
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
+ }
4518
4580
  /** 一覧の取得失敗を端末の消失へ丸めず、正規PTY一覧で存在を確認する。 */
4519
4581
  function authSessionExists(name) {
4520
4582
  return listSessionsResult().sessions.some(session => session.session_id === name);
@@ -4557,7 +4619,7 @@ export async function authenticateAgent(kind, options) {
4557
4619
  const meta = loadAuthMetadata(options.session_id, kind);
4558
4620
  if (!authSessionExists(options.session_id)) {
4559
4621
  if (meta)
4560
- fs.unlinkSync(authMetadataPath(options.session_id));
4622
+ withExecutionPreparation(options.session_id, () => fs.unlinkSync(authMetadataPath(options.session_id)));
4561
4623
  return result("blocked", null, "認証sessionは既に終了または取消済みです。");
4562
4624
  }
4563
4625
  if (!meta)
@@ -4588,7 +4650,13 @@ export async function authenticateAgent(kind, options) {
4588
4650
  const kept = tmux("set-option", "-t", name, "remain-on-exit", "on");
4589
4651
  if (kept.code !== 0)
4590
4652
  throw new AitermError("AGENT_AUTH_PANE_CONFIG_FAILED: 認証processの終了観測を設定できません。", 2);
4591
- 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 });
4592
4660
  launchAuthProcess(name, meta);
4593
4661
  }
4594
4662
  catch (error) {
@@ -4636,8 +4704,10 @@ export async function authenticateAgent(kind, options) {
4636
4704
  return result("authenticated", session, null);
4637
4705
  if (meta.phase === "onboarding")
4638
4706
  return result("blocked", session, "公式CLIの初回案内の完了を確認できません。");
4639
- meta.phase = "onboarding";
4640
- writeJson0600(authMetadataPath(session), meta);
4707
+ withExecutionPreparation(session, () => {
4708
+ meta.phase = "onboarding";
4709
+ writeJson0600(authMetadataPath(session), meta);
4710
+ });
4641
4711
  const restarted = tmux("respawn-pane", "-k", "-t", session, resolveWinPaneShell("bash"));
4642
4712
  if (restarted.code !== 0)
4643
4713
  return result("failed", session, "公式CLIの初回案内を起動できません。");
@@ -5032,8 +5102,10 @@ const restoreCursorDefaults = (configPath) => (before) => {
5032
5102
  restoreJsonSetting(configPath, before, CURSOR_DEFAULT_MODEL_KEYS);
5033
5103
  };
5034
5104
  function recordAgentFast(meta, fast) {
5035
- meta.fast = fast;
5036
- writeAgentMetadata(meta);
5105
+ withExecutionPreparation(meta.aiterm_session, () => {
5106
+ meta.fast = fast;
5107
+ writeAgentMetadata(meta);
5108
+ });
5037
5109
  }
5038
5110
  /** 動いている席のfastを切り替える。返りは切り替えた後の状態。席は入力待ちである事(呼び手が確かめる)。 */
5039
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,45 @@ 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
+ return view(state, record);
972
+ if (record.operations.launch === "issued")
973
+ throw new ExecutionError("EXECUTION_BUSY");
974
+ if (record.stopping || stopped(state, args.target) || stopped(state, relation.source?.launch)
975
+ || interruptionBlocksRequest(state, args.request))
976
+ throw new ExecutionError("EXECUTION_STOPPING");
977
+ const slots = state.slots.filter(slot => slot.session_id === args.target.session_id);
978
+ const slot = slots[0];
979
+ if (record.operations.launch !== "unknown" || record.operations.send !== "not_issued"
980
+ || relation.phase !== "submitted_unknown" || relation.completion !== "unknown" || relation.lifecycle !== "tracking"
981
+ || record.reservation.target.session_id !== args.target.session_id
982
+ || relation.target?.boundary != null || (relation.target && !same(relation.target.launch, args.target))
983
+ || (record.reservation.target.expected_launch_id !== null && record.reservation.target.expected_launch_id !== args.target.launch_id)
984
+ || slots.length !== 1 || !slot || !same(slot.request, args.request) || slot.stage !== "launch" || slot.state !== "unknown"
985
+ || !slot.created_native_id || !slot.created_server
986
+ || (slot.expected_launch_id !== null && slot.expected_launch_id !== args.target.launch_id)
987
+ || openParentEffect(state, args.request))
988
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
989
+ if (!this.runtime.observeCreatedLaunchSync)
990
+ throw new ExecutionError("EXECUTION_UNSUPPORTED");
991
+ const parsed = createdLaunchObservationSchema.safeParse(this.runtime.observeCreatedLaunchSync(args.target));
992
+ if (!parsed.success || !same(parsed.data.target, args.target)
993
+ || parsed.data.native.native_id !== slot.created_native_id
994
+ || !isDeepStrictEqual(parsed.data.native.server, slot.created_server))
995
+ throw new ExecutionError("EXECUTION_UNCONFIRMED");
996
+ record.operations.launch = "accepted";
997
+ relation.target = { launch: args.target, boundary: null };
998
+ relation.phase = "invoked";
999
+ relation.completion = "open";
1000
+ state.slots = state.slots.filter(value => value !== slot);
1001
+ return view(state, record);
1002
+ });
1003
+ }
956
1004
  async finish(args) {
957
1005
  return this.store.transaction(async (state) => {
958
1006
  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
@@ -29,19 +29,24 @@ POSIXのraw tty回復もnative作用であり、tmux打鍵と同じ受付を通
29
29
  実stty直前にも同じdeviceを確認する。停止や世代/tty交代後に古いdeviceへ回復を発行しない。
30
30
  一つのRPCが複数のnative作用を発行するときも、各回に同じissued slotとsource/target世代・停止を再確認する。
31
31
  agentの貼付け・Enterの同期区間は、既存metadata mutexを非同期に取得してから実行する。
32
+ native作成後に再開する起動準備も同じ非同期取得を通す。pane所有検査、fg/stty回復、
33
+ 起動メニューの各キーを短い同期区間で実行し、待機後の停止・世代・slot検査を省略しない。
32
34
  mutexをawait中へ持ち越さず、同じ同期stack内の各gateが発行記録を永続化してからnative作用へ進む。
33
35
  検証済みsourceと同世代への通常sendがissuedである間も、そのsourceの送信先が別ならoutgoing RPCは継続できる。
34
36
  incomingのslotを解放・書換・成功扱いせず、unknown・管理操作・世代不一致は従来どおり拒否する。
35
37
  incomingの完了待ちをoutgoingへ課して相互送信を循環待ちにしない(ADR 0127)。
36
38
  BellTeamのpromptなし明示管理launchは、管理close同様にserving process/request identityへ結合する。
37
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)。
38
43
  新launchは、既存POSIX serverのnative owner、またはserver未作成の完全な空観測を受付時と発行直前に確認する。
39
44
  冷間時は最初のnew-session自身でserverを作り、その実receiptのnative IDとserver PIDを、作成後の安定したnative一覧とprocess開始識別子で照合する。
40
45
  既存server時も冷間時も、作成したsessionのnative ID・server owner・core内部のlaunch世代を同じ要求slotへ段階結合する。
41
46
  物理shell準備はnative IDへ固定し、未結合の同名世代を採用しない。冷間起動のための補助sessionや再送は作らない。
42
47
  作成時のnative ID/server ownerをmanaged metadataへ保存し、private runtimeの世代読取と期待closeでも再照合する。
43
48
  古いmetadataの欠測を現観測で補わず、既存席の自動登録は行わない。
44
- 証拠の無い新server起動、Windows固有のserver作成、auto-name/通常PTY/auth等はまだunsupported。
49
+ 証拠の無い新server起動、Windows固有のserver作成、一般のauto-name/通常PTY等はまだunsupported。認証専用PTYはADR 0130の限定経路で受け付ける。
45
50
  実18 toolsのhandlerを作るconstructorは、private dispatcher/親execution callbackを明示注入して隔離SDKで検証できる。
46
51
  通常のstdio mainは注入せず、全参加process/全core作用口・native fenceが揃うまで公開有効化しない。
47
52
  private participant factoryはproducer/service/dispatcher/同process用worker profileを同じscopeとnative actorで組む。
@@ -403,6 +408,14 @@ shell、接続先、各harnessの公式CLIが所有する。
403
408
  ## Model catalog
404
409
 
405
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
+
406
419
 
407
420
  PTYが消失した場合は、相関記録の有無にかかわらず`status`が`failed`、`cancel`が既に終了・取消済みを示す`blocked`を返し、どちらも`session_id:null`となる。保存したsession IDを解除して`start`で再開始できる。生存中の通常PTYやharness不一致、記録の破損・読取り失敗はエラーを返す。
408
421
 
@@ -440,6 +453,13 @@ unknown send枠を保持したまま既存core closeへ進み、native close成
440
453
  旧callbackは未知/消失した元slotで拒否され、同IDの新launchへnative作用を発行できない。
441
454
  issued、管理close自体のunknown、親作用のslot、世代/native/serverの不明・交代は対象外。
442
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
+ issued、入力済み・不明、停止、別slot、世代/native/serverの不一致は拒否。後続sendは元source世代を含め通常受付を通る。
462
+ 遅着した元callbackはまだ所有するissued stage/slotがある時だけunknownを書け、照合後や別stageを上書きしない。
443
463
  native handlerのisErrorは元の本文を保持し、`aiterm/execution_error`のcode/effectを併記する。
444
464
  native作用後の不明はpossibleのままで、原因説明を再送可否や成功receiptへ変換しない。
445
465
  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.5",
3
+ "version": "0.61.7",
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": [