tailii-host 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/backend/herdr.js +8 -1
  2. package/dist/backend/herdr.js.map +1 -1
  3. package/dist/backend/tmux.js +90 -15
  4. package/dist/backend/tmux.js.map +1 -1
  5. package/dist/chat/subagentTranscript.js +4 -0
  6. package/dist/chat/subagentTranscript.js.map +1 -1
  7. package/dist/chat/transcriptTailer.js +8 -0
  8. package/dist/chat/transcriptTailer.js.map +1 -1
  9. package/dist/codex/codexAppServer.d.ts +74 -3
  10. package/dist/codex/codexAppServer.js +252 -12
  11. package/dist/codex/codexAppServer.js.map +1 -1
  12. package/dist/codex/codexNativeTurnController.d.ts +99 -2
  13. package/dist/codex/codexNativeTurnController.js +218 -8
  14. package/dist/codex/codexNativeTurnController.js.map +1 -1
  15. package/dist/codex/codexPlanItem.d.ts +14 -0
  16. package/dist/codex/codexPlanItem.js +50 -0
  17. package/dist/codex/codexPlanItem.js.map +1 -0
  18. package/dist/codex/codexRolloutTailer.d.ts +10 -0
  19. package/dist/codex/codexRolloutTailer.js +60 -2
  20. package/dist/codex/codexRolloutTailer.js.map +1 -1
  21. package/dist/codex/codexSystemNotice.d.ts +22 -1
  22. package/dist/codex/codexSystemNotice.js +46 -0
  23. package/dist/codex/codexSystemNotice.js.map +1 -1
  24. package/dist/engine/engine.js +1 -0
  25. package/dist/engine/engine.js.map +1 -1
  26. package/dist/engine/handlers/codex.js +36 -1
  27. package/dist/engine/handlers/codex.js.map +1 -1
  28. package/dist/hub/hubProtocol.d.ts +22 -1
  29. package/dist/hub/hubProtocol.js +53 -1
  30. package/dist/hub/hubProtocol.js.map +1 -1
  31. package/dist/hub/sessionHub.d.ts +12 -0
  32. package/dist/hub/sessionHub.js +96 -5
  33. package/dist/hub/sessionHub.js.map +1 -1
  34. package/dist/protocol/decode.js +52 -0
  35. package/dist/protocol/decode.js.map +1 -1
  36. package/dist/protocol/messages.d.ts +58 -0
  37. package/dist/sessions/claudeSessionStore.js +4 -0
  38. package/dist/sessions/claudeSessionStore.js.map +1 -1
  39. package/dist/sessions/sessionSearch.js +5 -0
  40. package/dist/sessions/sessionSearch.js.map +1 -1
  41. package/dist/shared/clientBuild.d.ts +1 -1
  42. package/dist/shared/clientBuild.js +1 -1
  43. package/dist/shared/clientBuild.js.map +1 -1
  44. package/dist/shared/compactSummary.d.ts +9 -0
  45. package/dist/shared/compactSummary.js +16 -0
  46. package/dist/shared/compactSummary.js.map +1 -0
  47. package/package.json +3 -3
@@ -12,7 +12,7 @@ import { PROTOCOL_V1, PROTOCOL_V2 } from "../protocol.js";
12
12
  import { ChatInjectionRejectedError, LoginCodeError } from "../backend/tmux.js";
13
13
  import { CodexAppServerManager } from "../codex/codexAppServer.js";
14
14
  import { CodexNativeTurnController, chatContentKey, } from "../codex/codexNativeTurnController.js";
15
- import { CONTEXT_STREAM_ID as CODEX_CONTEXT_STREAM_ID, CONTEXT_WINDOW_STREAM_ID as CODEX_CONTEXT_WINDOW_STREAM_ID, MODEL_STREAM_ID as CODEX_MODEL_STREAM_ID, } from "../codex/codexRolloutTailer.js";
15
+ import { COLLABORATION_MODE_STREAM_ID as CODEX_COLLABORATION_MODE_STREAM_ID, CONTEXT_STREAM_ID as CODEX_CONTEXT_STREAM_ID, CONTEXT_WINDOW_STREAM_ID as CODEX_CONTEXT_WINDOW_STREAM_ID, MODEL_STREAM_ID as CODEX_MODEL_STREAM_ID, } from "../codex/codexRolloutTailer.js";
16
16
  import { claudeTranscriptPathFor } from "../sessions/transcriptPath.js";
17
17
  /**
18
18
  * backfilling 中の購読者は publishConversationEvent が丸ごと飛ばす。旗が立ちっぱなしになると
@@ -510,6 +510,40 @@ export class SessionHub {
510
510
  void this.drainCodexQueue(message.session, actor);
511
511
  return;
512
512
  }
513
+ if (message.type === "codex_goal_submit") {
514
+ // 目標操作(codex-goal)。turn と同じ hub 所有の thread 接続で実行し、結果と通知を会話 stream へ返す。
515
+ const controller = this.ensureCodexTurnController();
516
+ const goal = controller.goal;
517
+ if (goal === undefined) {
518
+ this.sendTo(client, {
519
+ type: "codex_goal_result", id: message.id, status: "failed",
520
+ error: "この host の Codex 連携は目標(goal)に対応していません。",
521
+ });
522
+ return;
523
+ }
524
+ void goal.call(controller, {
525
+ session: message.session, threadId: message.threadId, cwd: message.cwd, action: message.action,
526
+ ...(message.objective !== undefined ? { objective: message.objective } : {}),
527
+ ...(message.status !== undefined ? { status: message.status } : {}),
528
+ ...(message.tokenBudget !== undefined ? { tokenBudget: message.tokenBudget } : {}),
529
+ }).then((result) => {
530
+ this.sendTo(client, {
531
+ type: "codex_goal_result", id: message.id, status: "ok",
532
+ ...(result.goal !== null ? { goal: result.goal } : {}),
533
+ ...(result.cleared !== undefined ? { cleared: result.cleared } : {}),
534
+ });
535
+ // 解除 / 完了後に購読者も turn も無ければ、目標のために保持していた thread 接続を畳む。
536
+ this.maybeCloseIdleCodexSession(message.session);
537
+ }, (error) => {
538
+ this.options.log?.(`codex goal ${message.action} 失敗 session=${message.session}: ${String(error)}`);
539
+ this.sendTo(client, {
540
+ type: "codex_goal_result", id: message.id, status: "failed", error: String(error),
541
+ });
542
+ // 失敗でも、この操作のために開いただけの thread 接続は残さない。
543
+ this.maybeCloseIdleCodexSession(message.session);
544
+ });
545
+ return;
546
+ }
513
547
  if (message.type === "codex_turn_interrupt") {
514
548
  void this.codexTurnController?.interruptTurn?.(message.session).catch((error) => {
515
549
  this.publishCodexMarker(message.session, `codex-interrupt-error-${message.id}`, `⚠️ Codex turn 中断失敗: ${String(error)}`);
@@ -881,10 +915,22 @@ export class SessionHub {
881
915
  actor.tail?.stop();
882
916
  actor.tail = null;
883
917
  actor.codexLive = null;
884
- if (!this.activeCodexTurns.has(session))
885
- this.codexTurnController?.closeSession(session);
918
+ this.maybeCloseIdleCodexSession(session);
886
919
  }
887
920
  }
921
+ /**
922
+ * 購読者ゼロ・turn なし・active な目標なし、のときだけ Codex thread 接続を畳む。
923
+ * 目標が active な間は購読者がいなくても保持する(継続 turn を観測するため, codex-goal)。
924
+ */
925
+ maybeCloseIdleCodexSession(session) {
926
+ if ((this.actors.get(session)?.subscribers.size ?? 0) > 0)
927
+ return;
928
+ if (this.activeCodexTurns.has(session))
929
+ return;
930
+ if (this.codexTurnController?.hasActiveGoal?.(session) === true)
931
+ return;
932
+ this.codexTurnController?.closeSession(session);
933
+ }
888
934
  startSharedTail(session, actor, newerThanMs) {
889
935
  const meta = this.options.metadataStore.get(session);
890
936
  if (meta === null) {
@@ -933,6 +979,8 @@ export class SessionHub {
933
979
  lastModel: null,
934
980
  subscribedModel: null,
935
981
  scanLastModel: null,
982
+ subscribedCollaborationMode: null,
983
+ lastCollaborationMode: null,
936
984
  };
937
985
  actor.codexLive = state;
938
986
  // 履歴表示は rollout だけで完結する。App Server の購読(daemon 起動・接続・thread/resume・
@@ -955,6 +1003,7 @@ export class SessionHub {
955
1003
  // 会話を開いていない間の /model や設定変更は thread/settings/updated が誰にも届かない。
956
1004
  // resume 応答の現在モデルを backfill 完了後に配信し、開き直しで表示を実体へ揃える。
957
1005
  state.subscribedModel = snapshot.model ?? null;
1006
+ state.subscribedCollaborationMode = snapshot.collaborationMode ?? null;
958
1007
  if (!snapshot.liveSubscribed) {
959
1008
  this.options.log?.(`Codex App Server の履歴スナップショットが読めない(turn 前の未materialize か履歴読み取り失敗)ため、rollout fallbackへ移行 session=${session} thread=${threadId}` +
960
1009
  (snapshot.liveSubscriptionError ? `(理由: ${snapshot.liveSubscriptionError})` : ""));
@@ -1006,6 +1055,14 @@ export class SessionHub {
1006
1055
  // 処理中状態を自己修復できない。
1007
1056
  }
1008
1057
  this.publishCodexModelMarker(session, actor, state, subscribedModel);
1058
+ // thread の現在の collaboration mode(controller が把握している場合)。履歴(task_started)由来の値は
1059
+ // iOS が採用しないので、実体はここで 1 回配る(codex-plan-mode)。
1060
+ const subscribedCollaborationMode = state.subscribedCollaborationMode;
1061
+ state.subscribedCollaborationMode = null;
1062
+ if (subscribedCollaborationMode !== null) {
1063
+ state.lastCollaborationMode = subscribedCollaborationMode;
1064
+ this.publishCodexMarker(session, CODEX_COLLABORATION_MODE_STREAM_ID, subscribedCollaborationMode);
1065
+ }
1009
1066
  }
1010
1067
  openCodexRollout(session, actor, cwd, threadId, newerThanMs) {
1011
1068
  if (this.options.tailFactory === undefined)
@@ -1307,6 +1364,15 @@ export class SessionHub {
1307
1364
  type: "conversation_event", session, serverSeq: 0, payload: codexModelMarker(currentModel),
1308
1365
  });
1309
1366
  }
1367
+ // collaboration mode も同様(turn を伴わない `/plan` 等は境界前の live 配信にしか無い, codex-plan-mode)。
1368
+ const currentCollaborationMode = actor.codexLive?.lastCollaborationMode ?? null;
1369
+ if (currentCollaborationMode !== null) {
1370
+ this.sendTo(client, {
1371
+ type: "conversation_event", session, serverSeq: 0,
1372
+ payload: { type: "chat_output", v: PROTOCOL_V1, streamId: CODEX_COLLABORATION_MODE_STREAM_ID,
1373
+ role: "system", text: currentCollaborationMode, eof: true },
1374
+ });
1375
+ }
1310
1376
  };
1311
1377
  // 進捗は「最後に 1 行流れた時刻」で測る。履歴行ごとに setTimeout を張り替えると
1312
1378
  // 巨大な transcript でタイマー生成が支配的になるため、タイマーは 1 本のまま残時間を見る。
@@ -1706,9 +1772,29 @@ export class SessionHub {
1706
1772
  this.activeCodexTurns.delete(session);
1707
1773
  this.applyProcessing(session, state);
1708
1774
  this.broadcast({ type: "session_processing", session, state });
1709
- if (state === "done" && (this.actors.get(session)?.subscribers.size ?? 0) === 0) {
1710
- this.codexTurnController?.closeSession(session);
1775
+ // 購読者ゼロで turn が終われば接続を畳む。ただし目標(goal)が active な会話は畳まない:
1776
+ // 継続 turn は server 起点で、購読を落とすと処理中 / heartbeat / 完了通知を観測できない
1777
+ // (reaper が idle 扱いで pane を掃除する)ため(codex-goal)。
1778
+ if (state === "done")
1779
+ this.maybeCloseIdleCodexSession(session);
1780
+ },
1781
+ onGoal: (session, goal) => {
1782
+ // 目標の現在値を会話 stream に載せる(replay buffer 経由で後から加わった購読者にも届く)。
1783
+ this.publishConversationEvent(session, this.actor(session), {
1784
+ type: "codex_goal_state", v: PROTOCOL_V2, session, ...(goal !== null ? { goal } : {}),
1785
+ });
1786
+ if (goal === null || goal.status !== "active")
1787
+ this.maybeCloseIdleCodexSession(session);
1788
+ },
1789
+ onCollaborationMode: (session, mode) => {
1790
+ // backfill 中は iOS が live 行を history-done まで捨てるため、finish で配る値も最新へ更新しておく。
1791
+ const live = this.actors.get(session)?.codexLive ?? null;
1792
+ if (live !== null) {
1793
+ live.lastCollaborationMode = mode;
1794
+ if (live.phase === "backfill")
1795
+ live.subscribedCollaborationMode = mode;
1711
1796
  }
1797
+ this.publishCodexMarker(session, CODEX_COLLABORATION_MODE_STREAM_ID, mode);
1712
1798
  },
1713
1799
  onModel: (session, model) => this.publishCodexMarker(session, CODEX_MODEL_STREAM_ID, model),
1714
1800
  onTokenUsage: (session, totalTokens, contextWindow) => {
@@ -1801,6 +1887,10 @@ export class SessionHub {
1801
1887
  effort: message.effort,
1802
1888
  approvalPolicy: message.approvalPolicy,
1803
1889
  sandbox: message.sandbox,
1890
+ // collaboration mode(プランモード, codex-plan-mode)。旧 engine は付けないので従来どおり省略。
1891
+ ...(message.collaborationMode !== undefined && message.collaborationMode !== null
1892
+ ? { collaborationMode: message.collaborationMode }
1893
+ : {}),
1804
1894
  });
1805
1895
  const actor = this.actors.get(message.session);
1806
1896
  if (actor === undefined || actor !== expectedActor)
@@ -2428,6 +2518,7 @@ function sameCodexRetry(left, right) {
2428
2518
  left.clientUserMessageId === right.clientUserMessageId && left.effort === right.effort &&
2429
2519
  (left.approvalPolicy ?? null) === (right.approvalPolicy ?? null) &&
2430
2520
  left.sandbox === right.sandbox &&
2521
+ (left.collaborationMode ?? null) === (right.collaborationMode ?? null) &&
2431
2522
  left.threadId === right.threadId && left.cwd === right.cwd;
2432
2523
  }
2433
2524
  /** 利用中モデル通知(`pc:model`)の conversation_event payload。 */