tailii-host 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -14
- package/dist/{broker.d.ts → approval/broker.d.ts} +9 -5
- package/dist/{broker.js → approval/broker.js} +42 -6
- package/dist/approval/broker.js.map +1 -0
- package/dist/{hook.d.ts → approval/hook.d.ts} +48 -7
- package/dist/{hook.js → approval/hook.js} +359 -50
- package/dist/approval/hook.js.map +1 -0
- package/dist/backend/herdr.d.ts +226 -0
- package/dist/backend/herdr.js +749 -0
- package/dist/backend/herdr.js.map +1 -0
- package/dist/backend/sessionBackend.d.ts +81 -0
- package/dist/backend/sessionBackend.js +114 -0
- package/dist/backend/sessionBackend.js.map +1 -0
- package/dist/backend/tmux.d.ts +125 -0
- package/dist/backend/tmux.js +392 -0
- package/dist/backend/tmux.js.map +1 -0
- package/dist/{chatTailController.d.ts → chat/chatTailController.d.ts} +29 -6
- package/dist/{chatTailController.js → chat/chatTailController.js} +94 -18
- package/dist/chat/chatTailController.js.map +1 -0
- package/dist/{imageService.d.ts → chat/imageService.d.ts} +12 -1
- package/dist/{imageService.js → chat/imageService.js} +39 -11
- package/dist/chat/imageService.js.map +1 -0
- package/dist/chat/remoteQuestionMonitor.d.ts +27 -0
- package/dist/chat/remoteQuestionMonitor.js +128 -0
- package/dist/chat/remoteQuestionMonitor.js.map +1 -0
- package/dist/chat/subagentTailer.d.ts +30 -0
- package/dist/chat/subagentTailer.js +704 -0
- package/dist/chat/subagentTailer.js.map +1 -0
- package/dist/chat/subagentTranscript.d.ts +20 -0
- package/dist/chat/subagentTranscript.js +273 -0
- package/dist/chat/subagentTranscript.js.map +1 -0
- package/dist/{transcriptTailer.d.ts → chat/transcriptTailer.d.ts} +12 -1
- package/dist/{transcriptTailer.js → chat/transcriptTailer.js} +183 -10
- package/dist/chat/transcriptTailer.js.map +1 -0
- package/dist/cli.js +15 -10
- package/dist/cli.js.map +1 -1
- package/dist/codex/codexAccountUsage.d.ts +22 -0
- package/dist/codex/codexAccountUsage.js +128 -0
- package/dist/codex/codexAccountUsage.js.map +1 -0
- package/dist/codex/codexAppServer.d.ts +166 -0
- package/dist/codex/codexAppServer.js +1297 -0
- package/dist/codex/codexAppServer.js.map +1 -0
- package/dist/codex/codexNativeTurnController.d.ts +153 -0
- package/dist/codex/codexNativeTurnController.js +673 -0
- package/dist/codex/codexNativeTurnController.js.map +1 -0
- package/dist/codex/codexRolloutTailer.d.ts +100 -0
- package/dist/codex/codexRolloutTailer.js +568 -0
- package/dist/codex/codexRolloutTailer.js.map +1 -0
- package/dist/{codexSessionStore.d.ts → codex/codexSessionStore.d.ts} +10 -2
- package/dist/{codexSessionStore.js → codex/codexSessionStore.js} +184 -15
- package/dist/codex/codexSessionStore.js.map +1 -0
- package/dist/codex/codexSubagentTracker.d.ts +21 -0
- package/dist/codex/codexSubagentTracker.js +281 -0
- package/dist/codex/codexSubagentTracker.js.map +1 -0
- package/dist/codex/codexToolActivity.d.ts +69 -0
- package/dist/codex/codexToolActivity.js +425 -0
- package/dist/codex/codexToolActivity.js.map +1 -0
- package/dist/{codexUsage.d.ts → codex/codexUsage.d.ts} +1 -1
- package/dist/{codexUsage.js → codex/codexUsage.js} +19 -26
- package/dist/codex/codexUsage.js.map +1 -0
- package/dist/commands/doctor.d.ts +65 -0
- package/dist/commands/doctor.js +425 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/{hookSettings.d.ts → commands/hookSettings.d.ts} +14 -1
- package/dist/{hookSettings.js → commands/hookSettings.js} +96 -12
- package/dist/commands/hookSettings.js.map +1 -0
- package/dist/{kick.js → commands/kick.js} +16 -1
- package/dist/commands/kick.js.map +1 -0
- package/dist/{launch.d.ts → commands/launch.d.ts} +67 -5
- package/dist/commands/launch.js +716 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/legacyHomeMigration.js.map +1 -0
- package/dist/{pairingCode.d.ts → commands/pairingCode.d.ts} +29 -2
- package/dist/{pairingCode.js → commands/pairingCode.js} +120 -11
- package/dist/commands/pairingCode.js.map +1 -0
- package/dist/{pushTokenCommand.d.ts → commands/pushTokenCommand.d.ts} +1 -1
- package/dist/{pushTokenCommand.js → commands/pushTokenCommand.js} +2 -2
- package/dist/commands/pushTokenCommand.js.map +1 -0
- package/dist/{setup.d.ts → commands/setup.d.ts} +17 -6
- package/dist/{setup.js → commands/setup.js} +133 -75
- package/dist/commands/setup.js.map +1 -0
- package/dist/engine/context.d.ts +145 -0
- package/dist/engine/context.js +184 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/engine.d.ts +121 -0
- package/dist/engine/engine.js +799 -0
- package/dist/engine/engine.js.map +1 -0
- package/dist/engine/handlers/accountUsage.d.ts +53 -0
- package/dist/engine/handlers/accountUsage.js +203 -0
- package/dist/engine/handlers/accountUsage.js.map +1 -0
- package/dist/engine/handlers/claudeModels.d.ts +2 -0
- package/dist/engine/handlers/claudeModels.js +23 -0
- package/dist/engine/handlers/claudeModels.js.map +1 -0
- package/dist/engine/handlers/codex.d.ts +2 -0
- package/dist/engine/handlers/codex.js +97 -0
- package/dist/engine/handlers/codex.js.map +1 -0
- package/dist/engine/handlers/conversation.d.ts +2 -0
- package/dist/engine/handlers/conversation.js +259 -0
- package/dist/engine/handlers/conversation.js.map +1 -0
- package/dist/engine/handlers/core.d.ts +2 -0
- package/dist/engine/handlers/core.js +66 -0
- package/dist/engine/handlers/core.js.map +1 -0
- package/dist/engine/handlers/git.d.ts +2 -0
- package/dist/engine/handlers/git.js +127 -0
- package/dist/engine/handlers/git.js.map +1 -0
- package/dist/engine/handlers/index.d.ts +2 -0
- package/dist/engine/handlers/index.js +32 -0
- package/dist/engine/handlers/index.js.map +1 -0
- package/dist/engine/handlers/mode.d.ts +2 -0
- package/dist/engine/handlers/mode.js +194 -0
- package/dist/engine/handlers/mode.js.map +1 -0
- package/dist/engine/handlers/officialApps.d.ts +2 -0
- package/dist/engine/handlers/officialApps.js +79 -0
- package/dist/engine/handlers/officialApps.js.map +1 -0
- package/dist/engine/handlers/preview.d.ts +2 -0
- package/dist/engine/handlers/preview.js +59 -0
- package/dist/engine/handlers/preview.js.map +1 -0
- package/dist/engine/handlers/session.d.ts +2 -0
- package/dist/engine/handlers/session.js +385 -0
- package/dist/engine/handlers/session.js.map +1 -0
- package/dist/engine/handlers/usage.d.ts +2 -0
- package/dist/engine/handlers/usage.js +96 -0
- package/dist/engine/handlers/usage.js.map +1 -0
- package/dist/engine/handlers/workspace.d.ts +2 -0
- package/dist/engine/handlers/workspace.js +110 -0
- package/dist/engine/handlers/workspace.js.map +1 -0
- package/dist/engine/slashCommands.d.ts +3 -0
- package/dist/engine/slashCommands.js +211 -0
- package/dist/engine/slashCommands.js.map +1 -0
- package/dist/hub/engineRelaySocket.d.ts +58 -0
- package/dist/hub/engineRelaySocket.js +161 -0
- package/dist/hub/engineRelaySocket.js.map +1 -0
- package/dist/hub/hubClient.d.ts +28 -0
- package/dist/hub/hubClient.js +187 -0
- package/dist/hub/hubClient.js.map +1 -0
- package/dist/hub/hubDaemon.d.ts +69 -0
- package/dist/hub/hubDaemon.js +466 -0
- package/dist/hub/hubDaemon.js.map +1 -0
- package/dist/hub/hubProtocol.d.ts +161 -0
- package/dist/hub/hubProtocol.js +379 -0
- package/dist/hub/hubProtocol.js.map +1 -0
- package/dist/hub/panePreviewPump.d.ts +65 -0
- package/dist/hub/panePreviewPump.js +187 -0
- package/dist/hub/panePreviewPump.js.map +1 -0
- package/dist/hub/questionInjection.d.ts +19 -0
- package/dist/hub/questionInjection.js +126 -0
- package/dist/hub/questionInjection.js.map +1 -0
- package/dist/hub/reaper.d.ts +74 -0
- package/dist/hub/reaper.js +333 -0
- package/dist/hub/reaper.js.map +1 -0
- package/dist/hub/sessionHub.d.ts +203 -0
- package/dist/hub/sessionHub.js +1862 -0
- package/dist/hub/sessionHub.js.map +1 -0
- package/dist/protocol/common.d.ts +18 -0
- package/dist/protocol/common.js +78 -0
- package/dist/protocol/common.js.map +1 -0
- package/dist/protocol/decode.d.ts +3 -0
- package/dist/protocol/decode.js +1036 -0
- package/dist/protocol/decode.js.map +1 -0
- package/dist/protocol/encode.d.ts +3 -0
- package/dist/protocol/encode.js +84 -0
- package/dist/protocol/encode.js.map +1 -0
- package/dist/protocol/messages.d.ts +1002 -0
- package/dist/protocol/messages.js +8 -0
- package/dist/protocol/messages.js.map +1 -0
- package/dist/protocol.d.ts +4 -329
- package/dist/protocol.js +13 -531
- package/dist/protocol.js.map +1 -1
- package/dist/push/apnsConfigStore.js.map +1 -0
- package/dist/push/apnsJwtProvider.js.map +1 -0
- package/dist/{apnsSender.js.map → push/apnsSender.js.map} +1 -1
- package/dist/{approvalPushNotifier.js → push/approvalPushNotifier.js} +1 -1
- package/dist/push/approvalPushNotifier.js.map +1 -0
- package/dist/push/deviceTokenStore.js.map +1 -0
- package/dist/{pushTypes.js → push/pushTypes.js} +1 -1
- package/dist/push/pushTypes.js.map +1 -0
- package/dist/push/sendLog.js.map +1 -0
- package/dist/services/accountIdentity.d.ts +44 -0
- package/dist/services/accountIdentity.js +184 -0
- package/dist/services/accountIdentity.js.map +1 -0
- package/dist/services/claudeCredentialRefresh.d.ts +30 -0
- package/dist/services/claudeCredentialRefresh.js +345 -0
- package/dist/services/claudeCredentialRefresh.js.map +1 -0
- package/dist/services/claudeModelCatalog.d.ts +21 -0
- package/dist/services/claudeModelCatalog.js +181 -0
- package/dist/services/claudeModelCatalog.js.map +1 -0
- package/dist/{dirLister.d.ts → services/dirLister.d.ts} +9 -4
- package/dist/services/dirLister.js +169 -0
- package/dist/services/dirLister.js.map +1 -0
- package/dist/services/fileService.d.ts +10 -0
- package/dist/services/fileService.js +123 -0
- package/dist/services/fileService.js.map +1 -0
- package/dist/services/gitService.d.ts +79 -0
- package/dist/services/gitService.js +648 -0
- package/dist/services/gitService.js.map +1 -0
- package/dist/services/hostVersions.d.ts +38 -0
- package/dist/services/hostVersions.js +120 -0
- package/dist/services/hostVersions.js.map +1 -0
- package/dist/services/officialApps.d.ts +148 -0
- package/dist/services/officialApps.js +1079 -0
- package/dist/services/officialApps.js.map +1 -0
- package/dist/services/planUsageFetcher.d.ts +119 -0
- package/dist/services/planUsageFetcher.js +357 -0
- package/dist/services/planUsageFetcher.js.map +1 -0
- package/dist/services/previewServer.d.ts +31 -0
- package/dist/services/previewServer.js +233 -0
- package/dist/services/previewServer.js.map +1 -0
- package/dist/services/quicGateway.d.ts +143 -0
- package/dist/services/quicGateway.js +292 -0
- package/dist/services/quicGateway.js.map +1 -0
- package/dist/services/serveService.d.ts +50 -0
- package/dist/services/serveService.js +302 -0
- package/dist/services/serveService.js.map +1 -0
- package/dist/services/usageAggregator.js.map +1 -0
- package/dist/sessions/claudeSessionStore.d.ts +80 -0
- package/dist/sessions/claudeSessionStore.js +477 -0
- package/dist/sessions/claudeSessionStore.js.map +1 -0
- package/dist/sessions/heartbeat.d.ts +24 -0
- package/dist/sessions/heartbeat.js +86 -0
- package/dist/sessions/heartbeat.js.map +1 -0
- package/dist/sessions/liveSessionJoin.d.ts +20 -0
- package/dist/sessions/liveSessionJoin.js +80 -0
- package/dist/sessions/liveSessionJoin.js.map +1 -0
- package/dist/sessions/sessionActivityProvider.d.ts +14 -0
- package/dist/sessions/sessionActivityProvider.js +57 -0
- package/dist/sessions/sessionActivityProvider.js.map +1 -0
- package/dist/{sessionListService.d.ts → sessions/sessionListService.d.ts} +3 -3
- package/dist/{sessionListService.js → sessions/sessionListService.js} +23 -6
- package/dist/sessions/sessionListService.js.map +1 -0
- package/dist/{sessionMetadataStore.d.ts → sessions/sessionMetadataStore.d.ts} +18 -0
- package/dist/{sessionMetadataStore.js → sessions/sessionMetadataStore.js} +36 -1
- package/dist/sessions/sessionMetadataStore.js.map +1 -0
- package/dist/{sessionSearch.d.ts → sessions/sessionSearch.d.ts} +1 -1
- package/dist/sessions/sessionSearch.js.map +1 -0
- package/dist/shared/hostDisplayName.d.ts +11 -0
- package/dist/shared/hostDisplayName.js +50 -0
- package/dist/shared/hostDisplayName.js.map +1 -0
- package/dist/{lineWriter.d.ts → shared/lineWriter.d.ts} +1 -1
- package/dist/{lineWriter.js → shared/lineWriter.js} +1 -1
- package/dist/shared/lineWriter.js.map +1 -0
- package/dist/{observationLog.js.map → shared/observationLog.js.map} +1 -1
- package/dist/{paths.d.ts → shared/paths.d.ts} +7 -0
- package/dist/{paths.js → shared/paths.js} +9 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/permissionMode.d.ts +10 -0
- package/dist/shared/permissionMode.js +51 -0
- package/dist/shared/permissionMode.js.map +1 -0
- package/dist/shared/skillInjection.d.ts +9 -0
- package/dist/shared/skillInjection.js +16 -0
- package/dist/shared/skillInjection.js.map +1 -0
- package/dist/shared/sleep.js.map +1 -0
- package/dist/{socketPath.d.ts → shared/socketPath.d.ts} +4 -0
- package/dist/{socketPath.js → shared/socketPath.js} +22 -0
- package/dist/shared/socketPath.js.map +1 -0
- package/dist/shared/version.js.map +1 -0
- package/package.json +13 -7
- package/dist/apnsConfigStore.js.map +0 -1
- package/dist/apnsJwtProvider.js.map +0 -1
- package/dist/approvalPushNotifier.js.map +0 -1
- package/dist/broker.js.map +0 -1
- package/dist/chatTailController.js.map +0 -1
- package/dist/claudeSessionStore.d.ts +0 -15
- package/dist/claudeSessionStore.js +0 -201
- package/dist/claudeSessionStore.js.map +0 -1
- package/dist/codexRolloutTailer.d.ts +0 -56
- package/dist/codexRolloutTailer.js +0 -303
- package/dist/codexRolloutTailer.js.map +0 -1
- package/dist/codexSessionStore.js.map +0 -1
- package/dist/codexUsage.js.map +0 -1
- package/dist/deviceTokenStore.js.map +0 -1
- package/dist/dirLister.js +0 -98
- package/dist/dirLister.js.map +0 -1
- package/dist/doctor.d.ts +0 -36
- package/dist/doctor.js +0 -162
- package/dist/doctor.js.map +0 -1
- package/dist/engine.d.ts +0 -85
- package/dist/engine.js +0 -962
- package/dist/engine.js.map +0 -1
- package/dist/hook.js.map +0 -1
- package/dist/hookSettings.js.map +0 -1
- package/dist/idleReaper.d.ts +0 -13
- package/dist/idleReaper.js +0 -30
- package/dist/idleReaper.js.map +0 -1
- package/dist/imageService.js.map +0 -1
- package/dist/kick.js.map +0 -1
- package/dist/launch.js +0 -396
- package/dist/launch.js.map +0 -1
- package/dist/legacyHomeMigration.js.map +0 -1
- package/dist/lineWriter.js.map +0 -1
- package/dist/mdnsAdvertiser.d.ts +0 -46
- package/dist/mdnsAdvertiser.js +0 -268
- package/dist/mdnsAdvertiser.js.map +0 -1
- package/dist/pairingCode.js.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/permissionMode.d.ts +0 -8
- package/dist/permissionMode.js +0 -32
- package/dist/permissionMode.js.map +0 -1
- package/dist/planUsageFetcher.d.ts +0 -35
- package/dist/planUsageFetcher.js +0 -172
- package/dist/planUsageFetcher.js.map +0 -1
- package/dist/pushTokenCommand.js.map +0 -1
- package/dist/pushTypes.js.map +0 -1
- package/dist/sendLog.js.map +0 -1
- package/dist/sessionActivityProvider.d.ts +0 -7
- package/dist/sessionActivityProvider.js +0 -44
- package/dist/sessionActivityProvider.js.map +0 -1
- package/dist/sessionIdleTracker.d.ts +0 -19
- package/dist/sessionIdleTracker.js +0 -42
- package/dist/sessionIdleTracker.js.map +0 -1
- package/dist/sessionListService.js.map +0 -1
- package/dist/sessionMetadataStore.js.map +0 -1
- package/dist/sessionSearch.js.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/sleep.js.map +0 -1
- package/dist/socketPath.js.map +0 -1
- package/dist/subagentTailer.d.ts +0 -15
- package/dist/subagentTailer.js +0 -399
- package/dist/subagentTailer.js.map +0 -1
- package/dist/tmux.d.ts +0 -62
- package/dist/tmux.js +0 -163
- package/dist/tmux.js.map +0 -1
- package/dist/transcriptTailer.js.map +0 -1
- package/dist/usageAggregator.js.map +0 -1
- package/dist/version.js.map +0 -1
- /package/dist/{kick.d.ts → commands/kick.d.ts} +0 -0
- /package/dist/{legacyHomeMigration.d.ts → commands/legacyHomeMigration.d.ts} +0 -0
- /package/dist/{legacyHomeMigration.js → commands/legacyHomeMigration.js} +0 -0
- /package/dist/{apnsConfigStore.d.ts → push/apnsConfigStore.d.ts} +0 -0
- /package/dist/{apnsConfigStore.js → push/apnsConfigStore.js} +0 -0
- /package/dist/{apnsJwtProvider.d.ts → push/apnsJwtProvider.d.ts} +0 -0
- /package/dist/{apnsJwtProvider.js → push/apnsJwtProvider.js} +0 -0
- /package/dist/{apnsSender.d.ts → push/apnsSender.d.ts} +0 -0
- /package/dist/{apnsSender.js → push/apnsSender.js} +0 -0
- /package/dist/{approvalPushNotifier.d.ts → push/approvalPushNotifier.d.ts} +0 -0
- /package/dist/{deviceTokenStore.d.ts → push/deviceTokenStore.d.ts} +0 -0
- /package/dist/{deviceTokenStore.js → push/deviceTokenStore.js} +0 -0
- /package/dist/{pushTypes.d.ts → push/pushTypes.d.ts} +0 -0
- /package/dist/{sendLog.d.ts → push/sendLog.d.ts} +0 -0
- /package/dist/{sendLog.js → push/sendLog.js} +0 -0
- /package/dist/{usageAggregator.d.ts → services/usageAggregator.d.ts} +0 -0
- /package/dist/{usageAggregator.js → services/usageAggregator.js} +0 -0
- /package/dist/{sessionSearch.js → sessions/sessionSearch.js} +0 -0
- /package/dist/{observationLog.d.ts → shared/observationLog.d.ts} +0 -0
- /package/dist/{observationLog.js → shared/observationLog.js} +0 -0
- /package/dist/{sleep.d.ts → shared/sleep.d.ts} +0 -0
- /package/dist/{sleep.js → shared/sleep.js} +0 -0
- /package/dist/{version.d.ts → shared/version.d.ts} +0 -0
- /package/dist/{version.js → shared/version.js} +0 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
// engine.ts
|
|
2
|
+
// tailii (TS host) — engine サブコマンド実装(EngineControl)
|
|
3
|
+
// Swift 版 Engine.swift の移植。
|
|
4
|
+
//
|
|
5
|
+
// セッション横断制御チャネル。stdin/stdout(SSH exec の stdio)で NDJSON 制御メッセージを
|
|
6
|
+
// 送受信し、session_* を TmuxSessionManager に橋渡しする。承認 socket は経由しない
|
|
7
|
+
// (横断制御を承認中継から位相分離)。
|
|
8
|
+
//
|
|
9
|
+
// 責務:
|
|
10
|
+
// - チャネル確立直後に channel_hello(maxVersion) を送出し、相手 hello 受信で
|
|
11
|
+
// 採用版 = min(双方 maxVersion) を決める(4.3)。
|
|
12
|
+
// - session_list_request / session_reattach / session_kill / session_start /
|
|
13
|
+
// session_idle_hint / usage / mode / image / dir / browse / claude-sessions の橋渡し。
|
|
14
|
+
// - decode 失敗行は破棄(承認文脈でないので単に無視、クラッシュしない)。
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import * as readline from "node:readline";
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
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";
|
|
21
|
+
import { fetchCodexAccountUsage, } from "../codex/codexAccountUsage.js";
|
|
22
|
+
import { CodexAppServerManager } from "../codex/codexAppServer.js";
|
|
23
|
+
import { CodexNativeTurnController, } from "../codex/codexNativeTurnController.js";
|
|
24
|
+
import { ClaudeSessionStore } from "../sessions/claudeSessionStore.js";
|
|
25
|
+
import { CodexSessionStore } from "../codex/codexSessionStore.js";
|
|
26
|
+
import { ensureHubDaemon } from "../hub/hubDaemon.js";
|
|
27
|
+
import { connectHubSocket } from "../hub/hubClient.js";
|
|
28
|
+
import { defaultHeartbeatDir } from "../sessions/heartbeat.js";
|
|
29
|
+
import { ImageService } from "../chat/imageService.js";
|
|
30
|
+
import { makeSessionLauncher } from "../commands/launch.js";
|
|
31
|
+
import { LineWriter } from "../shared/lineWriter.js";
|
|
32
|
+
import { fetchClaudeModelList } from "../services/claudeModelCatalog.js";
|
|
33
|
+
import { fetchAccountIdentities, } from "../services/accountIdentity.js";
|
|
34
|
+
import { fetchHostVersions } from "../services/hostVersions.js";
|
|
35
|
+
import { fetchPlanUsage } from "../services/planUsageFetcher.js";
|
|
36
|
+
import { PreviewServer } from "../services/previewServer.js";
|
|
37
|
+
import { decodeControlMessage, PROTOCOL_MAX_SUPPORTED, PROTOCOL_V1, } from "../protocol.js";
|
|
38
|
+
import { ownTranscriptActivityProvider } from "../sessions/sessionActivityProvider.js";
|
|
39
|
+
import { SessionListService } from "../sessions/sessionListService.js";
|
|
40
|
+
import { SessionMetadataStore } from "../sessions/sessionMetadataStore.js";
|
|
41
|
+
import { OfficialAppsService } from "../services/officialApps.js";
|
|
42
|
+
import { HISTORY_DONE_STREAM_ID, TranscriptTailer } from "../chat/transcriptTailer.js";
|
|
43
|
+
import { makeSessionBackend, resolveSessionBackendKind, writeSessionBackendKind, } from "../backend/sessionBackend.js";
|
|
44
|
+
import { herdrInstalled } from "../backend/herdr.js";
|
|
45
|
+
import { createStaleDistGuard, isStaleDist, readPackageVersion } from "../shared/version.js";
|
|
46
|
+
import { resolveHostDisplayName } from "../shared/hostDisplayName.js";
|
|
47
|
+
import { DEFAULT_MODE_TIMING, engineDiag, } from "./context.js";
|
|
48
|
+
import { ENGINE_HANDLERS } from "./handlers/index.js";
|
|
49
|
+
// MARK: - エントリポイント(cli から呼ばれる)
|
|
50
|
+
/**
|
|
51
|
+
* engine サブコマンドのエントリポイント。実 stdin/stdout を使って EngineControl を起動する。
|
|
52
|
+
* 引数(省略可、統合テスト注入用): --sessions-dir / --inner-command / --images-dir /
|
|
53
|
+
* --transcript / --claude-projects-dir / --idle-timeout / --resume-command。
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* 既定エージェントを host 側設定から解決する(`--agent` 未指定時のフォールバック)。
|
|
57
|
+
* `~/.tailii/agent` の内容が `codex` なら codex、それ以外/不在は claude。
|
|
58
|
+
* これにより iOS を改修せず(engine 起動コマンドは `<binary> engine` のまま)codex に切替できる。
|
|
59
|
+
*/
|
|
60
|
+
export function resolveDefaultAgent(agentFilePath = path.join(os.homedir(), ".tailii", "agent")) {
|
|
61
|
+
try {
|
|
62
|
+
const value = fs.readFileSync(agentFilePath, "utf8").trim().toLowerCase();
|
|
63
|
+
if (value === "codex")
|
|
64
|
+
return "codex";
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// 不在/読取失敗は既定 claude。
|
|
68
|
+
}
|
|
69
|
+
return "claude";
|
|
70
|
+
}
|
|
71
|
+
export async function runEngineCommand(args) {
|
|
72
|
+
process.stderr.write("[tailii-host engine] 起動(横断制御チャネル)\n");
|
|
73
|
+
let sessionsDirArg = null;
|
|
74
|
+
let innerCommandArg = null;
|
|
75
|
+
let imagesDirArg = null;
|
|
76
|
+
let transcriptArg = null;
|
|
77
|
+
let claudeProjectsDirArg = null;
|
|
78
|
+
let resumeCommandArg = null;
|
|
79
|
+
// 既定エージェントは host 側設定ファイルで切替可能(iOS 改修不要のトグル)。
|
|
80
|
+
// `--agent` フラグが渡ればそちらが優先する。
|
|
81
|
+
let agentArg = resolveDefaultAgent();
|
|
82
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
83
|
+
const next = () => (i + 1 < args.length ? args[++i] : null);
|
|
84
|
+
switch (args[i]) {
|
|
85
|
+
case "--sessions-dir":
|
|
86
|
+
sessionsDirArg = next();
|
|
87
|
+
break;
|
|
88
|
+
case "--inner-command":
|
|
89
|
+
innerCommandArg = next();
|
|
90
|
+
break;
|
|
91
|
+
case "--agent": {
|
|
92
|
+
const raw = next();
|
|
93
|
+
if (raw === "codex" || raw === "claude")
|
|
94
|
+
agentArg = raw;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "--images-dir":
|
|
98
|
+
imagesDirArg = next();
|
|
99
|
+
break;
|
|
100
|
+
case "--transcript":
|
|
101
|
+
transcriptArg = next();
|
|
102
|
+
break;
|
|
103
|
+
case "--claude-projects-dir":
|
|
104
|
+
claudeProjectsDirArg = next();
|
|
105
|
+
break;
|
|
106
|
+
case "--resume-command":
|
|
107
|
+
resumeCommandArg = next();
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// list と launch が同一の cwd 権威ストアを共有するよう、ここで1つだけ構築して両者へ渡す。
|
|
114
|
+
const store = new SessionMetadataStore(sessionsDirArg ?? undefined);
|
|
115
|
+
// 画像シーム: hook が投入した pending の drain と image_fetch_request の index 逆引き配信。
|
|
116
|
+
const imageService = imagesDirArg
|
|
117
|
+
? new ImageService({
|
|
118
|
+
pendingBase: path.join(imagesDirArg, "pending"),
|
|
119
|
+
indexBase: path.join(imagesDirArg, "index"),
|
|
120
|
+
})
|
|
121
|
+
: new ImageService();
|
|
122
|
+
// 会話出力シーム: --transcript 注入時だけ TranscriptTailer を構築する(後方互換)。
|
|
123
|
+
const transcriptTailer = transcriptArg ? new TranscriptTailer({ tailIndefinitely: true }) : null;
|
|
124
|
+
// claude 会話ルート(`~/.claude/projects`)。セッション連動 chat_output tail の解決基点。
|
|
125
|
+
const claudeProjectsRoot = claudeProjectsDirArg ?? path.join(os.homedir(), ".claude", "projects");
|
|
126
|
+
// 一覧整形(session-list-lifecycle): updatedAt 付与・整列・ページング。
|
|
127
|
+
// updatedAt の権威はセッション自身の会話 transcript(会話が無ければ最下位)。
|
|
128
|
+
// 端末バックエンド(tmux / herdr)は設定ファイル or アプリ(backend_set)で切替可能。
|
|
129
|
+
// 稼働系は常時 Composite(per-session ルーティング)なので、切替は再起動なしで
|
|
130
|
+
// 「次に起動するセッション」から反映され、既存セッションは元のバックエンドで操作し続ける。
|
|
131
|
+
const backendKind = resolveSessionBackendKind;
|
|
132
|
+
const sessionManager = makeSessionBackend({ store });
|
|
133
|
+
const claudeSessionStore = new ClaudeSessionStore(claudeProjectsRoot);
|
|
134
|
+
const codexSessionStore = new CodexSessionStore();
|
|
135
|
+
const sessionListService = new SessionListService(sessionManager, ownTranscriptActivityProvider({
|
|
136
|
+
metadataStore: store,
|
|
137
|
+
claudeStore: claudeSessionStore,
|
|
138
|
+
codexStore: codexSessionStore,
|
|
139
|
+
}));
|
|
140
|
+
// アイドルライフサイクル: kill 判定は Session Hub の周期 tick が担う。
|
|
141
|
+
// engine は heartbeat(判定権威ファイル)の書き手 + Hub 常駐の保証だけを行う。
|
|
142
|
+
ensureHubDaemon();
|
|
143
|
+
// resume 再起動 launcher(kill 済みセッションを記録 cwd で claude --continue 再起動)。
|
|
144
|
+
const resumeLauncher = makeSessionLauncher({
|
|
145
|
+
store,
|
|
146
|
+
innerCommand: resumeCommandArg ?? "claude --continue",
|
|
147
|
+
agent: "claude",
|
|
148
|
+
backend: backendKind,
|
|
149
|
+
claudeProjectsDir: claudeProjectsRoot,
|
|
150
|
+
});
|
|
151
|
+
// per-session: agentType=codex のセッション用に codex launcher / resume launcher を用意する。
|
|
152
|
+
// codex は resume 未対応のため既定コマンドで新規起動する(新しい rollout を tail)。
|
|
153
|
+
const codexAppServer = new CodexAppServerManager();
|
|
154
|
+
const codexLauncher = makeSessionLauncher({
|
|
155
|
+
store,
|
|
156
|
+
agent: "codex",
|
|
157
|
+
codexAppServer,
|
|
158
|
+
backend: backendKind,
|
|
159
|
+
});
|
|
160
|
+
const codexResumeLauncher = makeSessionLauncher({
|
|
161
|
+
store,
|
|
162
|
+
agent: "codex",
|
|
163
|
+
codexAppServer,
|
|
164
|
+
backend: backendKind,
|
|
165
|
+
});
|
|
166
|
+
try {
|
|
167
|
+
await runEngine({
|
|
168
|
+
input: process.stdin,
|
|
169
|
+
output: process.stdout,
|
|
170
|
+
sessionManager,
|
|
171
|
+
imageService,
|
|
172
|
+
transcriptTailer,
|
|
173
|
+
transcriptPath: transcriptArg,
|
|
174
|
+
// launcher は常に claude 版。codex は codexLauncher で分岐する(per-session)。
|
|
175
|
+
// agentType 未指定時にどちらへ倒すかは engine 内 defaultAgent(=agentArg) が決める。
|
|
176
|
+
launcher: makeSessionLauncher({
|
|
177
|
+
store,
|
|
178
|
+
innerCommand: innerCommandArg,
|
|
179
|
+
agent: "claude",
|
|
180
|
+
backend: backendKind,
|
|
181
|
+
claudeProjectsDir: claudeProjectsRoot,
|
|
182
|
+
}),
|
|
183
|
+
backendKind,
|
|
184
|
+
codexLauncher,
|
|
185
|
+
sessionListService,
|
|
186
|
+
metadataStore: store,
|
|
187
|
+
resumeLauncher,
|
|
188
|
+
codexResumeLauncher,
|
|
189
|
+
claudeSessionStore,
|
|
190
|
+
codexSessionStore,
|
|
191
|
+
codexAppServer,
|
|
192
|
+
officialApps: new OfficialAppsService({
|
|
193
|
+
codexRemoteControl: codexAppServer,
|
|
194
|
+
diagnosticLogPath: path.join(os.homedir(), ".tailii", "official-app.log"),
|
|
195
|
+
}),
|
|
196
|
+
agent: agentArg,
|
|
197
|
+
});
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
process.stderr.write(`[tailii-host engine] 異常終了: ${String(error)}\n`);
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* EngineControl の本体。`input` から NDJSON 行を読み、session_* を `sessionManager` に
|
|
207
|
+
* 橋渡しし、応答を `output` へ NDJSON 行で書く。EOF(input end)で正常終了する。
|
|
208
|
+
*/
|
|
209
|
+
export async function runEngine(options) {
|
|
210
|
+
const { sessionManager, backendKind = () => "tmux", backendWriter = writeSessionBackendKind, herdrInstalledProbe = herdrInstalled, imageService = null, transcriptTailer = null, transcriptPath = null, launcher = null, codexLauncher = null, sessionListService = null, metadataStore = null, resumeLauncher = null, codexResumeLauncher = null, claudeSessionStore = null, codexSessionStore = null, agent = "claude", codexAppServer = null, officialApps = new OfficialAppsService(), codexTurnController: injectedCodexTurnController = null, maxVersion = PROTOCOL_MAX_SUPPORTED, planUsage = () => fetchPlanUsage(), claudeModelList = () => fetchClaudeModelList(), codexAccountUsage: injectedCodexAccountUsage = undefined, hostVersions = fetchHostVersions, accountIdentity = fetchAccountIdentities, homeDir = os.homedir(), modeTiming = {}, staleDistGuard = createStaleDistGuard(), hostDisplayName = resolveHostDisplayName() ?? null, onStaleDist = undefined, hubLink: injectedHubLink = undefined, codexHubRpcTimeoutMs = 15_000, heartbeatDir = defaultHeartbeatDir(), } = options;
|
|
211
|
+
// 既定の Codex 使用量フェッチャは共有 App Server 経由(未接続・停止中は null を返す)。
|
|
212
|
+
// 分割代入の中では codexAppServer をまだ参照できないため、確定後にここで束ねる。
|
|
213
|
+
const codexAccountUsage = injectedCodexAccountUsage ?? (() => fetchCodexAccountUsage(codexAppServer));
|
|
214
|
+
const resolvedModeTiming = { ...DEFAULT_MODE_TIMING, ...modeTiming };
|
|
215
|
+
const resolvedHeartbeatDir = heartbeatDir ?? defaultHeartbeatDir();
|
|
216
|
+
const hubLink = injectedHubLink ?? connectHubSocket();
|
|
217
|
+
// 出力の直列化(Node の Writable は書込順序を保証する)。
|
|
218
|
+
const writer = new LineWriter(options.output);
|
|
219
|
+
// 採用版(negotiated version)。相手 hello 受信で min を採る。
|
|
220
|
+
const state = {
|
|
221
|
+
negotiatedVersion: maxVersion,
|
|
222
|
+
ownMaxVersion: maxVersion,
|
|
223
|
+
modeSetInFlight: new Set(),
|
|
224
|
+
};
|
|
225
|
+
const lifecycleAbort = new AbortController();
|
|
226
|
+
const background = [];
|
|
227
|
+
const activeChatSession = { name: null };
|
|
228
|
+
// 一覧 Mission Control の watch 状態。hub 再接続を跨いで再送するため engine 側でも保持する。
|
|
229
|
+
const listPreviewWatch = { enabled: false };
|
|
230
|
+
const lastServerSeq = new Map();
|
|
231
|
+
// socket close 時刻ではなく、engine channel へ最後に書き切った会話 event の時刻。
|
|
232
|
+
// Hub 世代変更時の transcript backfill 境界として session ごとに保持する。
|
|
233
|
+
const lastForwardedAtMs = new Map();
|
|
234
|
+
let hubBootId = null;
|
|
235
|
+
// Hub ブロードキャストから作る接続ローカル read-model。
|
|
236
|
+
const processingSessions = new Map();
|
|
237
|
+
// フォーカス外でも処理中の会話だけを購読し、一覧表示中のログキャッシュを更新する。
|
|
238
|
+
// Hub 接続単位で上限を設け、異常な processing 通知でも購読を無制限に増やさない。
|
|
239
|
+
const backgroundChatSessions = new Set();
|
|
240
|
+
// preview=false 中に iOS へ route できない event が来た場合、その直前 seq を保持する。
|
|
241
|
+
// 後続 chat/tool を表示できても gap を飛び越えて checkpoint を進めず、前面復帰時に
|
|
242
|
+
// Hub replay から image/subagent を含む連続区間を回収する。
|
|
243
|
+
const backgroundReplayFloors = new Map();
|
|
244
|
+
// serverSeq=0 は transcript/rollout の履歴 backfill。完了マーカー前に Hub 世代が
|
|
245
|
+
// 変わった場合は時刻境界を使わず全履歴を再開し、古い未配送行を落とさない。
|
|
246
|
+
const historyBackfillSessions = new Set();
|
|
247
|
+
const backgroundUnwatchTimers = new Map();
|
|
248
|
+
const unwatchBackgroundSession = (session) => {
|
|
249
|
+
const timer = backgroundUnwatchTimers.get(session);
|
|
250
|
+
if (timer !== undefined)
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
backgroundUnwatchTimers.delete(session);
|
|
253
|
+
if (!backgroundChatSessions.delete(session))
|
|
254
|
+
return;
|
|
255
|
+
if (activeChatSession.name !== session) {
|
|
256
|
+
hubLink.send({ type: "conversation_unsubscribe", session });
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const watchBackgroundSession = (session, newerThanMs, subscribe = true) => {
|
|
260
|
+
const timer = backgroundUnwatchTimers.get(session);
|
|
261
|
+
if (timer !== undefined)
|
|
262
|
+
clearTimeout(timer);
|
|
263
|
+
backgroundUnwatchTimers.delete(session);
|
|
264
|
+
if (activeChatSession.name === session)
|
|
265
|
+
return;
|
|
266
|
+
if (!backgroundChatSessions.has(session) && backgroundChatSessions.size >= 16) {
|
|
267
|
+
const oldest = backgroundChatSessions.values().next().value;
|
|
268
|
+
if (oldest !== undefined)
|
|
269
|
+
unwatchBackgroundSession(oldest);
|
|
270
|
+
}
|
|
271
|
+
backgroundChatSessions.add(session);
|
|
272
|
+
if (!subscribe)
|
|
273
|
+
return;
|
|
274
|
+
hubLink.send({
|
|
275
|
+
type: "conversation_subscribe",
|
|
276
|
+
session,
|
|
277
|
+
...(newerThanMs !== undefined
|
|
278
|
+
? { newerThanMs }
|
|
279
|
+
: lastServerSeq.has(session) ? { afterSeq: lastServerSeq.get(session) } : {}),
|
|
280
|
+
preview: false,
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
const finishWatchingBackgroundSession = (session) => {
|
|
284
|
+
if (activeChatSession.name === session || !backgroundChatSessions.has(session))
|
|
285
|
+
return;
|
|
286
|
+
const previous = backgroundUnwatchTimers.get(session);
|
|
287
|
+
if (previous !== undefined)
|
|
288
|
+
clearTimeout(previous);
|
|
289
|
+
// processing=done と transcript tail の最終 event は別経路なので、短い猶予を置いて
|
|
290
|
+
// 最終 assistant/tool 出力を取りこぼさずキャッシュへ合流させる。
|
|
291
|
+
const timer = setTimeout(() => unwatchBackgroundSession(session), 2_000);
|
|
292
|
+
timer.unref();
|
|
293
|
+
backgroundUnwatchTimers.set(session, timer);
|
|
294
|
+
};
|
|
295
|
+
let codexTurnController = injectedCodexTurnController;
|
|
296
|
+
let previewServer = null;
|
|
297
|
+
try {
|
|
298
|
+
// ---- 1. channel_hello を送出(確立直後)----
|
|
299
|
+
const helloVersion = staleDistGuard?.startupVersion ?? readPackageVersion() ?? undefined;
|
|
300
|
+
writer.write({
|
|
301
|
+
type: "channel_hello",
|
|
302
|
+
v: PROTOCOL_V1,
|
|
303
|
+
maxVersion,
|
|
304
|
+
...(helloVersion !== undefined ? { serverVersion: helloVersion } : {}),
|
|
305
|
+
// マシン名(iOS の接続先一覧タイトルの供給源)。取れない環境では省略する。
|
|
306
|
+
...(hostDisplayName !== null ? { hostName: hostDisplayName } : {}),
|
|
307
|
+
});
|
|
308
|
+
// ---- 1.5 画像 pending を drain し image_available を送出 ----
|
|
309
|
+
if (imageService !== null) {
|
|
310
|
+
for (const message of await imageService.drainPending()) {
|
|
311
|
+
writer.write(message);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// 設問の解決を iOS へ届ける(前面会話= question_dismiss / 別会話= remote_pending_cleared)。
|
|
315
|
+
const dismissQuestion = (session, id) => {
|
|
316
|
+
const wire = session === activeChatSession.name
|
|
317
|
+
? { type: "question_dismiss", v: PROTOCOL_V1, id }
|
|
318
|
+
: { type: "remote_pending_cleared", v: PROTOCOL_V1, id, session, kind: "question" };
|
|
319
|
+
try {
|
|
320
|
+
writer.write(wire);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
process.stderr.write(`[tailii-host engine] question dismiss 書込失敗: ${String(error)}\n`);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
// 設問イベントの反映(question-hook-relay)。前面会話ならネイティブ設問シート
|
|
327
|
+
// (question_prompt)、別会話なら一覧バッジ(remote_pending kind=question)へ変換する。
|
|
328
|
+
const handleQuestionEvent = (message) => {
|
|
329
|
+
if (message.event === "dismiss") {
|
|
330
|
+
dismissQuestion(message.session, message.id);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const questions = message.questions ?? [];
|
|
334
|
+
const first = questions[0];
|
|
335
|
+
const wire = message.session === activeChatSession.name
|
|
336
|
+
? { type: "question_prompt", v: PROTOCOL_V1, id: message.id, questions }
|
|
337
|
+
: {
|
|
338
|
+
type: "remote_pending",
|
|
339
|
+
v: PROTOCOL_V1,
|
|
340
|
+
id: message.id,
|
|
341
|
+
session: message.session,
|
|
342
|
+
kind: "question",
|
|
343
|
+
summary: first?.question || first?.header || "Question prompt",
|
|
344
|
+
};
|
|
345
|
+
try {
|
|
346
|
+
writer.write(wire);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
process.stderr.write(`[tailii-host engine] question prompt 書込失敗: ${String(error)}\n`);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
// Hub ブロードキャストを接続ローカル read-model に反映する。
|
|
353
|
+
const handleSessionProcessing = (message) => {
|
|
354
|
+
const now = Math.floor(Date.now() / 1000);
|
|
355
|
+
if (message.state === "active") {
|
|
356
|
+
processingSessions.set(message.session, now);
|
|
357
|
+
watchBackgroundSession(message.session);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
processingSessions.delete(message.session);
|
|
361
|
+
finishWatchingBackgroundSession(message.session);
|
|
362
|
+
}
|
|
363
|
+
writer.write({
|
|
364
|
+
type: "session_processing_state",
|
|
365
|
+
v: state.negotiatedVersion,
|
|
366
|
+
session: message.session,
|
|
367
|
+
active: message.state === "active",
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
if (codexTurnController === null && codexAppServer !== null) {
|
|
371
|
+
const writeCodexMarker = (session, streamId, text) => {
|
|
372
|
+
// chat_output には session ID が無いため、現在開いている会話だけへ流す。
|
|
373
|
+
if (session !== activeChatSession.name)
|
|
374
|
+
return;
|
|
375
|
+
try {
|
|
376
|
+
writer.write({
|
|
377
|
+
type: "chat_output",
|
|
378
|
+
v: state.negotiatedVersion,
|
|
379
|
+
streamId,
|
|
380
|
+
role: "system",
|
|
381
|
+
text,
|
|
382
|
+
eof: true,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
process.stderr.write(`[tailii-host engine] Codex model/context marker 書込失敗: ${String(error)}\n`);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
codexTurnController = new CodexNativeTurnController({
|
|
390
|
+
appServer: codexAppServer,
|
|
391
|
+
onProcessing: (session, processingState) => {
|
|
392
|
+
hubLink.send({
|
|
393
|
+
type: "session_processing",
|
|
394
|
+
session,
|
|
395
|
+
state: processingState,
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
onModel: (session, model) => {
|
|
399
|
+
writeCodexMarker(session, CODEX_MODEL_STREAM_ID, model);
|
|
400
|
+
},
|
|
401
|
+
onTokenUsage: (session, totalTokens, contextWindow) => {
|
|
402
|
+
writeCodexMarker(session, CODEX_CONTEXT_STREAM_ID, String(totalTokens));
|
|
403
|
+
if (contextWindow !== null) {
|
|
404
|
+
writeCodexMarker(session, CODEX_CONTEXT_WINDOW_STREAM_ID, String(contextWindow));
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
onQuestion: ({ session, id, questions }) => {
|
|
408
|
+
handleQuestionEvent({ type: "question_event", session, event: "prompt", id, questions });
|
|
409
|
+
},
|
|
410
|
+
onQuestionDismiss: (session, id) => {
|
|
411
|
+
handleQuestionEvent({ type: "question_event", session, event: "dismiss", id });
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
const rpcWaiters = new Map();
|
|
416
|
+
const rpcDisconnectFailures = new Map();
|
|
417
|
+
hubLink.onMessage = (message) => {
|
|
418
|
+
if (message.type === "hub_hello_ack")
|
|
419
|
+
return;
|
|
420
|
+
if (message.type === "conversation_event") {
|
|
421
|
+
let payload = null;
|
|
422
|
+
const isActive = message.session === activeChatSession.name;
|
|
423
|
+
if (message.session === activeChatSession.name) {
|
|
424
|
+
payload = message.payload;
|
|
425
|
+
}
|
|
426
|
+
else if (backgroundChatSessions.has(message.session)) {
|
|
427
|
+
if (message.payload.type === "chat_stream_alias") {
|
|
428
|
+
payload = {
|
|
429
|
+
type: "session_chat_stream_alias",
|
|
430
|
+
v: state.negotiatedVersion,
|
|
431
|
+
session: message.session,
|
|
432
|
+
serverSeq: message.serverSeq,
|
|
433
|
+
streamId: message.payload.streamId,
|
|
434
|
+
aliasStreamIds: message.payload.aliasStreamIds,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
else if (message.payload.type === "chat_output") {
|
|
438
|
+
payload = {
|
|
439
|
+
type: "session_chat_output",
|
|
440
|
+
v: state.negotiatedVersion,
|
|
441
|
+
session: message.session,
|
|
442
|
+
serverSeq: message.serverSeq,
|
|
443
|
+
streamId: message.payload.streamId,
|
|
444
|
+
role: message.payload.role,
|
|
445
|
+
text: message.payload.text,
|
|
446
|
+
eof: message.payload.eof,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
else if (message.payload.type === "tool_activity") {
|
|
450
|
+
payload = {
|
|
451
|
+
type: "session_tool_activity",
|
|
452
|
+
v: state.negotiatedVersion,
|
|
453
|
+
session: message.session,
|
|
454
|
+
serverSeq: message.serverSeq,
|
|
455
|
+
activity: message.payload.activity,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
else if (!backgroundReplayFloors.has(message.session)) {
|
|
459
|
+
const floor = lastServerSeq.get(message.session) ?? Math.max(0, message.serverSeq - 1);
|
|
460
|
+
backgroundReplayFloors.set(message.session, floor);
|
|
461
|
+
lastServerSeq.set(message.session, floor);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (payload === null)
|
|
465
|
+
return;
|
|
466
|
+
try {
|
|
467
|
+
writer.write(payload);
|
|
468
|
+
if (message.serverSeq === 0) {
|
|
469
|
+
historyBackfillSessions.add(message.session);
|
|
470
|
+
if ((payload.type === "chat_output" || payload.type === "session_chat_output") &&
|
|
471
|
+
payload.streamId === HISTORY_DONE_STREAM_ID) {
|
|
472
|
+
historyBackfillSessions.delete(message.session);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (message.serverSeq > 0) {
|
|
476
|
+
lastForwardedAtMs.set(message.session, Date.now());
|
|
477
|
+
if (isActive) {
|
|
478
|
+
lastServerSeq.set(message.session, message.serverSeq);
|
|
479
|
+
backgroundReplayFloors.delete(message.session);
|
|
480
|
+
}
|
|
481
|
+
else if (!backgroundReplayFloors.has(message.session)) {
|
|
482
|
+
lastServerSeq.set(message.session, message.serverSeq);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
process.stderr.write(`[tailii-host engine] conversation_event 書込失敗: ${String(error)}\n`);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (message.type === "conversation_pane_preview") {
|
|
492
|
+
// 前面会話は常時。他会話は一覧 Mission Control の watch 有効中のみ(iOS は
|
|
493
|
+
// payload の session フィールドで一覧カードへルーティングする)。
|
|
494
|
+
if (message.session !== activeChatSession.name && !listPreviewWatch.enabled)
|
|
495
|
+
return;
|
|
496
|
+
try {
|
|
497
|
+
writer.write(message.payload);
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
process.stderr.write(`[tailii-host engine] pane_preview 書込失敗: ${String(error)}\n`);
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (message.type === "conversation_liveness") {
|
|
505
|
+
// live-pill Phase 2: 一覧 watch 中のみ「稼働中ピルを外して一覧を取り直せ」を送る。
|
|
506
|
+
// 前面会話の例外は要らない(チャット面は自身の生存を別経路で知る)。
|
|
507
|
+
if (!listPreviewWatch.enabled)
|
|
508
|
+
return;
|
|
509
|
+
try {
|
|
510
|
+
writer.write({
|
|
511
|
+
type: "session_liveness_event", v: state.negotiatedVersion,
|
|
512
|
+
session: message.session, alive: message.alive,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
process.stderr.write(`[tailii-host engine] session_liveness_event 書込失敗: ${String(error)}\n`);
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (message.type === "conversation_mode") {
|
|
521
|
+
// tmux 側で切り替わった permission mode の現況通知(mode_set_response 形式)。
|
|
522
|
+
if (message.session !== activeChatSession.name)
|
|
523
|
+
return;
|
|
524
|
+
try {
|
|
525
|
+
writer.write({ ...message.payload, v: state.negotiatedVersion });
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
process.stderr.write(`[tailii-host engine] mode_push 書込失敗: ${String(error)}\n`);
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (message.type === "hub_state_response" || message.type === "question_answer_result" ||
|
|
533
|
+
message.type === "input_claim_result" || message.type === "runtime_claim_result" ||
|
|
534
|
+
message.type === "codex_turn_result" || message.type === "chat_send_result" ||
|
|
535
|
+
message.type === "pending_message_delete_result" ||
|
|
536
|
+
message.type === "presence_response" ||
|
|
537
|
+
message.type === "conversation_subagent_transcript_response") {
|
|
538
|
+
rpcWaiters.get(message.id)?.(message);
|
|
539
|
+
rpcWaiters.delete(message.id);
|
|
540
|
+
rpcDisconnectFailures.delete(message.id);
|
|
541
|
+
}
|
|
542
|
+
else if (message.type === "session_processing")
|
|
543
|
+
handleSessionProcessing(message);
|
|
544
|
+
else if (message.type === "question_event")
|
|
545
|
+
handleQuestionEvent(message);
|
|
546
|
+
else {
|
|
547
|
+
try {
|
|
548
|
+
writer.write({ ...message, v: state.negotiatedVersion });
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
process.stderr.write(`[tailii-host engine] remote_pending 書込失敗: ${String(error)}\n`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const hubRpc = (request, id, timeoutMs) => new Promise((resolve, reject) => {
|
|
556
|
+
const timer = timeoutMs > 0
|
|
557
|
+
? setTimeout(() => {
|
|
558
|
+
rpcWaiters.delete(id);
|
|
559
|
+
rpcDisconnectFailures.delete(id);
|
|
560
|
+
reject(new Error("Session Hub RPC timeout"));
|
|
561
|
+
}, timeoutMs)
|
|
562
|
+
: null;
|
|
563
|
+
rpcWaiters.set(id, (response) => {
|
|
564
|
+
if (timer !== null)
|
|
565
|
+
clearTimeout(timer);
|
|
566
|
+
rpcDisconnectFailures.delete(id);
|
|
567
|
+
resolve(response);
|
|
568
|
+
});
|
|
569
|
+
rpcDisconnectFailures.set(id, () => {
|
|
570
|
+
if (timer !== null)
|
|
571
|
+
clearTimeout(timer);
|
|
572
|
+
rpcWaiters.delete(id);
|
|
573
|
+
reject(new Error("Session Hub disconnected during RPC"));
|
|
574
|
+
});
|
|
575
|
+
// リンク不調で RPC が破棄された場合は待たずに即時失敗させる(chat_send が
|
|
576
|
+
// 黙って消えて応答が永遠に返らず、アプリのバブルが pending のまま固まる実障害)。
|
|
577
|
+
if (!hubLink.send(request)) {
|
|
578
|
+
if (timer !== null)
|
|
579
|
+
clearTimeout(timer);
|
|
580
|
+
rpcWaiters.delete(id);
|
|
581
|
+
rpcDisconnectFailures.delete(id);
|
|
582
|
+
reject(new Error("Session Hub link unavailable"));
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
hubLink.onReconnect = ({ bootId, disconnectedAtMs, processingSessions: processingSnapshot }) => {
|
|
586
|
+
if (disconnectedAtMs !== null) {
|
|
587
|
+
const failures = [...rpcDisconnectFailures.values()];
|
|
588
|
+
rpcDisconnectFailures.clear();
|
|
589
|
+
for (const fail of failures)
|
|
590
|
+
fail();
|
|
591
|
+
}
|
|
592
|
+
const restarted = hubBootId !== null && hubBootId !== bootId;
|
|
593
|
+
hubBootId = bootId;
|
|
594
|
+
const requiresFullBackfill = restarted ? new Set(historyBackfillSessions) : new Set();
|
|
595
|
+
if (restarted) {
|
|
596
|
+
// serverSeq は Hub 世代ローカル。現在未購読の会話も含め旧 checkpoint を全破棄し、
|
|
597
|
+
// 後日 open した会話が新世代の同じ数値を誤って afterSeq として使わないようにする。
|
|
598
|
+
lastServerSeq.clear();
|
|
599
|
+
backgroundReplayFloors.clear();
|
|
600
|
+
historyBackfillSessions.clear();
|
|
601
|
+
}
|
|
602
|
+
// hello snapshot を権威状態として扱う。切断中に完了した会話をローカル Map に
|
|
603
|
+
// active のまま残すと、一覧の処理中表示と背景購読が永久に残ってしまう。ただし
|
|
604
|
+
// processingSessions を送らない旧 Hub は「空」ではなく「snapshot 非対応」なので、
|
|
605
|
+
// その場合は既知状態を維持してローリング更新中の誤った inactive 化を避ける。
|
|
606
|
+
if (processingSnapshot !== undefined) {
|
|
607
|
+
const snapshotSet = new Set(processingSnapshot);
|
|
608
|
+
for (const session of [...processingSessions.keys()]) {
|
|
609
|
+
if (snapshotSet.has(session))
|
|
610
|
+
continue;
|
|
611
|
+
processingSessions.delete(session);
|
|
612
|
+
finishWatchingBackgroundSession(session);
|
|
613
|
+
writer.write({
|
|
614
|
+
type: "session_processing_state",
|
|
615
|
+
v: state.negotiatedVersion,
|
|
616
|
+
session,
|
|
617
|
+
active: false,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
for (const processingSession of processingSnapshot) {
|
|
621
|
+
processingSessions.set(processingSession, Math.floor(Date.now() / 1_000));
|
|
622
|
+
// この直後の sessions loop が再接続境界を揃えて一度だけ subscribe する。
|
|
623
|
+
watchBackgroundSession(processingSession, restarted ? disconnectedAtMs ?? undefined : undefined, false);
|
|
624
|
+
writer.write({
|
|
625
|
+
type: "session_processing_state",
|
|
626
|
+
v: state.negotiatedVersion,
|
|
627
|
+
session: processingSession,
|
|
628
|
+
active: true,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// 一覧 watch は hub 側 client 状態なので、再接続(hub 再起動含む)ごとに再送する。
|
|
633
|
+
if (listPreviewWatch.enabled) {
|
|
634
|
+
hubLink.send({ type: "session_preview_watch", enabled: true });
|
|
635
|
+
}
|
|
636
|
+
const sessions = new Set(backgroundChatSessions);
|
|
637
|
+
if (activeChatSession.name !== null)
|
|
638
|
+
sessions.add(activeChatSession.name);
|
|
639
|
+
for (const session of sessions) {
|
|
640
|
+
const preview = session === activeChatSession.name;
|
|
641
|
+
if (restarted) {
|
|
642
|
+
// serverSeq は Hub プロセス内の採番なので世代を越えて比較できない。切断時刻以降だけを
|
|
643
|
+
// transcript から backfill し、既表示本文の全履歴再送と停止中の追記欠落をともに避ける。
|
|
644
|
+
// socket close は最後に engine channel へ配送できた event より大幅に遅れて観測され
|
|
645
|
+
// うる。session ごとの最終成功時刻を権威境界にし、tail の書込→転送遅延ぶんだけ5秒
|
|
646
|
+
// 重ねる。まだ一度も配送していない会話だけ close 時刻の1分 rewindへ fallbackする。
|
|
647
|
+
const forwardedAtMs = lastForwardedAtMs.get(session);
|
|
648
|
+
const backfillBoundary = requiresFullBackfill.has(session)
|
|
649
|
+
? undefined
|
|
650
|
+
: forwardedAtMs !== undefined
|
|
651
|
+
? Math.max(0, forwardedAtMs - 5_000)
|
|
652
|
+
: disconnectedAtMs !== null ? Math.max(0, disconnectedAtMs - 60_000) : undefined;
|
|
653
|
+
hubLink.send({ type: "conversation_subscribe", session,
|
|
654
|
+
...(backfillBoundary !== undefined ? { newerThanMs: backfillBoundary } : {}), preview });
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
hubLink.send({ type: "conversation_subscribe", session,
|
|
658
|
+
...(lastServerSeq.has(session) ? { afterSeq: lastServerSeq.get(session) } : {}), preview });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
const requestHubState = (session) => {
|
|
663
|
+
const id = randomUUID();
|
|
664
|
+
return new Promise((resolve) => {
|
|
665
|
+
rpcWaiters.set(id, (raw) => {
|
|
666
|
+
const response = raw;
|
|
667
|
+
if (response.processing) {
|
|
668
|
+
processingSessions.set(session, Math.floor(Date.now() / 1000));
|
|
669
|
+
watchBackgroundSession(session);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
processingSessions.delete(session);
|
|
673
|
+
finishWatchingBackgroundSession(session);
|
|
674
|
+
}
|
|
675
|
+
writer.write({
|
|
676
|
+
type: "session_processing_state",
|
|
677
|
+
v: state.negotiatedVersion,
|
|
678
|
+
session,
|
|
679
|
+
active: response.processing,
|
|
680
|
+
});
|
|
681
|
+
resolve(response.pendingQuestion);
|
|
682
|
+
});
|
|
683
|
+
hubLink.send({ type: "hub_state_request", id, session });
|
|
684
|
+
});
|
|
685
|
+
};
|
|
686
|
+
// remoteQuestionMonitor は起動しない(question-hook-relay で陳腐化)。transcript には
|
|
687
|
+
// 回答済みの設問しか現れなくなったため、monitor が出せるのは「回答直後の
|
|
688
|
+
// remote_pending→cleared の一瞬のバッジ明滅」だけで有害無益。別会話の未回答設問の
|
|
689
|
+
// バッジは hook relay の question_event(handleQuestionEvent)が正しく賄う。
|
|
690
|
+
// ---- 1.6 会話出力 tail を開始し chat_output を engine チャネルへ逐次送出 ----
|
|
691
|
+
if (transcriptTailer !== null && transcriptPath !== null) {
|
|
692
|
+
background.push((async () => {
|
|
693
|
+
for await (const message of transcriptTailer.streamTranscript(transcriptPath, lifecycleAbort.signal)) {
|
|
694
|
+
if (lifecycleAbort.signal.aborted)
|
|
695
|
+
break;
|
|
696
|
+
try {
|
|
697
|
+
writer.write(message);
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
process.stderr.write(`[tailii-host engine] chat_output 書込失敗: ${String(error)}\n`);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
})());
|
|
705
|
+
}
|
|
706
|
+
// Web プレビュー: HTML ファイル配信用の loopback 静的サーバー(lazy、open まで待受なし)。
|
|
707
|
+
previewServer = new PreviewServer();
|
|
708
|
+
// ---- 2. 行読み取りループ ----
|
|
709
|
+
engineDiag(`engine readLoop 開始 pid=${process.pid}`);
|
|
710
|
+
const rl = readline.createInterface({ input: options.input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
711
|
+
try {
|
|
712
|
+
for await (const line of rl) {
|
|
713
|
+
const didProcessMessage = await handleLine(line, {
|
|
714
|
+
writer,
|
|
715
|
+
state,
|
|
716
|
+
sessionManager,
|
|
717
|
+
backendKind,
|
|
718
|
+
backendWriter,
|
|
719
|
+
herdrInstalledProbe,
|
|
720
|
+
imageService,
|
|
721
|
+
launcher,
|
|
722
|
+
codexLauncher,
|
|
723
|
+
sessionListService,
|
|
724
|
+
metadataStore,
|
|
725
|
+
hubLink,
|
|
726
|
+
requestHubState,
|
|
727
|
+
hubRpc,
|
|
728
|
+
codexHubRpcTimeoutMs,
|
|
729
|
+
heartbeatDir: resolvedHeartbeatDir,
|
|
730
|
+
resumeLauncher,
|
|
731
|
+
codexResumeLauncher,
|
|
732
|
+
claudeSessionStore,
|
|
733
|
+
codexSessionStore,
|
|
734
|
+
planUsage,
|
|
735
|
+
claudeModelList,
|
|
736
|
+
codexAccountUsage,
|
|
737
|
+
hostVersions,
|
|
738
|
+
accountIdentity,
|
|
739
|
+
homeDir,
|
|
740
|
+
modeTiming: resolvedModeTiming,
|
|
741
|
+
defaultAgent: agent,
|
|
742
|
+
activeChatSession,
|
|
743
|
+
listPreviewWatch,
|
|
744
|
+
processingSessions,
|
|
745
|
+
backgroundChatSessions,
|
|
746
|
+
lastServerSeq,
|
|
747
|
+
codexAppServer,
|
|
748
|
+
codexTurnController,
|
|
749
|
+
officialApps,
|
|
750
|
+
previewServer,
|
|
751
|
+
});
|
|
752
|
+
if (didProcessMessage && isStaleDist(staleDistGuard)) {
|
|
753
|
+
process.stderr.write("[tailii-host engine] stale dist を検出、再起動のため終了\n");
|
|
754
|
+
onStaleDist?.();
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
finally {
|
|
760
|
+
rl.close();
|
|
761
|
+
}
|
|
762
|
+
engineDiag(`engine readLoop EOF(チャネル断)pid=${process.pid}`);
|
|
763
|
+
}
|
|
764
|
+
finally {
|
|
765
|
+
// ---- 3. チャネル断で chat_output tail / reaper を確実に停止する(全経路) ----
|
|
766
|
+
lifecycleAbort.abort();
|
|
767
|
+
for (const timer of backgroundUnwatchTimers.values())
|
|
768
|
+
clearTimeout(timer);
|
|
769
|
+
backgroundUnwatchTimers.clear();
|
|
770
|
+
hubLink.close();
|
|
771
|
+
codexTurnController?.close();
|
|
772
|
+
await previewServer?.closeAll();
|
|
773
|
+
await Promise.allSettled(background);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// MARK: - 1 行の処理
|
|
777
|
+
/** 1行(改行なし)をデコードし、type に対応するドメインハンドラへ dispatch する。decode 失敗は破棄。 */
|
|
778
|
+
async function handleLine(rawLine, ctx) {
|
|
779
|
+
const trimmed = rawLine.replaceAll("\r", "");
|
|
780
|
+
if (!trimmed)
|
|
781
|
+
return false;
|
|
782
|
+
let message;
|
|
783
|
+
try {
|
|
784
|
+
message = decodeControlMessage(trimmed);
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
// 破棄(不正 JSON / 未知 type / 非対応版 / 必須欠落)。承認文脈でないので無視。
|
|
788
|
+
engineDiag(`engine decode 失敗(行破棄): ${String(error)} 生=${trimmed.slice(0, 120)}`);
|
|
789
|
+
process.stderr.write(`[tailii-host engine] decode 失敗、行破棄: ${String(error)}\n`);
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
engineDiag(`engine 受信 type=${message.type}`);
|
|
793
|
+
// 承認2型・画像・chat_output など registry 外の type は engine 制御では処理しない(破棄)。
|
|
794
|
+
const handler = ENGINE_HANDLERS[message.type];
|
|
795
|
+
if (handler !== undefined)
|
|
796
|
+
await handler(message, ctx);
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
//# sourceMappingURL=engine.js.map
|