@cocorograph/hub-agent 0.7.79 → 0.7.81
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/package.json +1 -1
- package/src/claude-stream-bridge.mjs +208 -55
- package/src/codex-stream-bridge.mjs +509 -115
- package/src/main.mjs +31 -4
- package/src/service-install.mjs +5 -37
- package/templates/co.cocorograph.hub-agent.plist +1 -1
- package/templates/hub-agent.service +1 -1
package/src/main.mjs
CHANGED
|
@@ -2064,14 +2064,22 @@ export function startStateLoop({ client, plugins, logger, intervalMs, claudeBrid
|
|
|
2064
2064
|
const tick = async () => {
|
|
2065
2065
|
if (stopped) return
|
|
2066
2066
|
try {
|
|
2067
|
-
// B11:
|
|
2067
|
+
// B11: 多端末共有 (T06542 で常時) で、クラッシュして detach を送らず消えた死端末を GC する
|
|
2068
2068
|
// (活性なし TTL 超過の購読端末キーを掃除し、最後の 1 台が外れたら idle softDetach へ)。
|
|
2069
|
-
//
|
|
2069
|
+
// 例外は state loop 全体の try/catch が拾う。
|
|
2070
2070
|
try {
|
|
2071
2071
|
claudeBridge?.gcDeadTerminals?.()
|
|
2072
2072
|
} catch (err) {
|
|
2073
2073
|
logger?.warn({ err: err?.message }, "gcDeadTerminals failed")
|
|
2074
2074
|
}
|
|
2075
|
+
// 子⑤ (T06544): claude 側 warm 境界(per-cwd=1 / N-cwd LRU)。従来 claude は per-session の
|
|
2076
|
+
// softDetach タイマー(7 日)だけで件数上限が無かったため、多数の cwd を渡り歩くと warm な
|
|
2077
|
+
// SDK 常駐 query が蓄積し得た。codex の reconcileSessions と対で cwd 単位に有界化する。
|
|
2078
|
+
try {
|
|
2079
|
+
claudeBridge?.reconcileWarmBounds?.()
|
|
2080
|
+
} catch (err) {
|
|
2081
|
+
logger?.warn({ err: err?.message }, "claude reconcileWarmBounds failed")
|
|
2082
|
+
}
|
|
2075
2083
|
// Codex 版 (claude の gcDeadTerminals と対): detach を送らず消えた / turn/completed が
|
|
2076
2084
|
// 来ず activeTurnId が固着したスタックセッションの app-server を回収する (プロセス
|
|
2077
2085
|
// リーク・生成中固着の根治)。進行中の正常ターンは app-server 通知で touch され残る。
|
|
@@ -2559,6 +2567,13 @@ function makeSeenInputIdRegistry(max = SEEN_INPUT_IDS_MAX) {
|
|
|
2559
2567
|
const seenClaudeChatInputIds = makeSeenInputIdRegistry()
|
|
2560
2568
|
const seenCodexChatInputIds = makeSeenInputIdRegistry()
|
|
2561
2569
|
|
|
2570
|
+
// round4 major#1 (T06542): codex.input の **in-flight** dedup 用レジストリ (input_id -> Promise)。
|
|
2571
|
+
// `seen` は settle 後にしか登録されないため、frontend の ack watchdog (5s) が bridge の barrier/RPC
|
|
2572
|
+
// (turn/start→turn/started は数秒〜) より短く、同一 input_id が settle 前に再送されると bridge.input を
|
|
2573
|
+
// 二重に呼んで FIFO へ重複投入され、二重ターン/steer になる。処理中の input_id は再 dispatch せず既存
|
|
2574
|
+
// Promise へ合流させる (settle 時に元の .then/.catch が 1 回だけ ack/nack を出す)。
|
|
2575
|
+
const codexInflightByInputId = new Map()
|
|
2576
|
+
|
|
2562
2577
|
/**
|
|
2563
2578
|
* input_id 付き pty.data (tracked 入力) の処理。write 成功で pty.input.ack を返し、
|
|
2564
2579
|
* stream 不在なら pty.error を返して browser の再 attach → 再送に繋げる。
|
|
@@ -2697,13 +2712,19 @@ export function handleCodexChatInput(msg, ctx) {
|
|
|
2697
2712
|
return undefined
|
|
2698
2713
|
}
|
|
2699
2714
|
if (input_id && seenCodexChatInputIds.has(input_id)) {
|
|
2700
|
-
// 再送 (ack 喪失想定)。turn/start・turn/steer を再送出せず ack のみ返す
|
|
2715
|
+
// 再送 (ack 喪失想定 / settle 済み)。turn/start・turn/steer を再送出せず ack のみ返す
|
|
2701
2716
|
// (二重ターン実行を避ける。claude.input dedup と同じ考え方)。
|
|
2702
2717
|
ctx.logger?.info?.({ stream_id, input_id }, "codex.input dedup (ack only)")
|
|
2703
2718
|
ctx.client.send({ type: "codex.input.ack", stream_id, input_id })
|
|
2704
2719
|
return undefined
|
|
2705
2720
|
}
|
|
2706
|
-
|
|
2721
|
+
if (input_id && codexInflightByInputId.has(input_id)) {
|
|
2722
|
+
// round4 major#1: **処理中** の同一 input_id (watchdog の settle 前再送)。再 dispatch せず既存
|
|
2723
|
+
// Promise へ合流する (元の .then/.catch が settle 時に 1 回だけ ack/nack を出すので二重投入しない)。
|
|
2724
|
+
ctx.logger?.info?.({ stream_id, input_id }, "codex.input in-flight dedup (join, no re-dispatch)")
|
|
2725
|
+
return codexInflightByInputId.get(input_id)
|
|
2726
|
+
}
|
|
2727
|
+
const p = ctx.codexBridge
|
|
2707
2728
|
.input({
|
|
2708
2729
|
stream_id,
|
|
2709
2730
|
message: msg.message,
|
|
@@ -2735,6 +2756,12 @@ export function handleCodexChatInput(msg, ctx) {
|
|
|
2735
2756
|
...(err.reason ? { reason: err.reason } : {}),
|
|
2736
2757
|
})
|
|
2737
2758
|
})
|
|
2759
|
+
.finally(() => {
|
|
2760
|
+
// in-flight レジストリから外す (成功時は seen が以降の再送を dedup、失敗時は再送で正当にリトライ)。
|
|
2761
|
+
if (input_id) codexInflightByInputId.delete(input_id)
|
|
2762
|
+
})
|
|
2763
|
+
if (input_id) codexInflightByInputId.set(input_id, p)
|
|
2764
|
+
return p
|
|
2738
2765
|
}
|
|
2739
2766
|
|
|
2740
2767
|
/**
|
package/src/service-install.mjs
CHANGED
|
@@ -15,8 +15,6 @@ import path from "node:path"
|
|
|
15
15
|
import { spawnSync } from "node:child_process"
|
|
16
16
|
import { fileURLToPath } from "node:url"
|
|
17
17
|
|
|
18
|
-
import { readConfig } from "./config.mjs"
|
|
19
|
-
|
|
20
18
|
const PLIST_LABEL = "co.cocorograph.hub-agent"
|
|
21
19
|
const SYSTEMD_UNIT_NAME = "hub-agent.service"
|
|
22
20
|
// Windows(WSL) のログオン keep-alive タスク名 (install.ps1 の $TASK_NAME と一致)。
|
|
@@ -55,7 +53,7 @@ function escapeXmlText(s) {
|
|
|
55
53
|
.replaceAll(">", ">")
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
function expandTemplate(text, hubAgentBin
|
|
56
|
+
function expandTemplate(text, hubAgentBin) {
|
|
59
57
|
// hub-agent デーモンの runtime 環境に必須な追加 env を、各プラットフォームの
|
|
60
58
|
// 起動ファイル形式 (plist / systemd) に合わせて差し込むためのプレースホルダ展開。
|
|
61
59
|
//
|
|
@@ -78,22 +76,8 @@ function expandTemplate(text, hubAgentBin, opts = {}) {
|
|
|
78
76
|
systemdLine = `Environment=NODE_EXTRA_CA_CERTS=${nodeExtraCa}\n`
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
//
|
|
82
|
-
// plist
|
|
83
|
-
// が agent.json.chat_shared を読んで渡す opts.chatShared、または (b) install-service
|
|
84
|
-
// 実行時の process.env.HUB_AGENT_CHAT_SHARED。これにより agent 更新で install-service
|
|
85
|
-
// が plist を再生成しても、agent.json に chat_shared=true があればフラグが維持される
|
|
86
|
-
// (旧実装は手動 plist 追記が再生成で消える事故があった。2026-05-30)。
|
|
87
|
-
const chatShared =
|
|
88
|
-
opts.chatShared === true || process.env.HUB_AGENT_CHAT_SHARED === "1"
|
|
89
|
-
let chatPlistEntry = ""
|
|
90
|
-
let chatSystemdLine = ""
|
|
91
|
-
if (chatShared) {
|
|
92
|
-
chatPlistEntry =
|
|
93
|
-
` <key>HUB_AGENT_CHAT_SHARED</key>\n` +
|
|
94
|
-
` <string>1</string>\n`
|
|
95
|
-
chatSystemdLine = `Environment=HUB_AGENT_CHAT_SHARED=1\n`
|
|
96
|
-
}
|
|
79
|
+
// T06542: 多端末共有チャットは既定 ON 化しフラグ (HUB_AGENT_CHAT_SHARED) を撤去した。
|
|
80
|
+
// plist/systemd への env 焼き込みも不要になったため、テンプレートの placeholder ごと削除済み。
|
|
97
81
|
|
|
98
82
|
return text
|
|
99
83
|
.replaceAll("__HUB_AGENT_BIN__", hubAgentBin)
|
|
@@ -101,19 +85,6 @@ function expandTemplate(text, hubAgentBin, opts = {}) {
|
|
|
101
85
|
.replaceAll("__PATH__", process.env.PATH || "/usr/local/bin:/usr/bin:/bin")
|
|
102
86
|
.replaceAll("__NODE_EXTRA_CA_CERTS_PLIST_ENTRY__", plistEntry)
|
|
103
87
|
.replaceAll("__NODE_EXTRA_CA_CERTS_SYSTEMD_LINE__", systemdLine)
|
|
104
|
-
.replaceAll("__HUB_AGENT_CHAT_SHARED_PLIST_ENTRY__", chatPlistEntry)
|
|
105
|
-
.replaceAll("__HUB_AGENT_CHAT_SHARED_SYSTEMD_LINE__", chatSystemdLine)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** agent.json から多端末共有フラグ (chat_shared) を読む。読めなければ false。
|
|
109
|
-
* install-service 時に plist/systemd へ焼き込む値ソースに使う (env 併用は expandTemplate 側)。 */
|
|
110
|
-
async function readChatSharedFromConfig() {
|
|
111
|
-
try {
|
|
112
|
-
const config = await readConfig()
|
|
113
|
-
return config?.chat_shared === true
|
|
114
|
-
} catch {
|
|
115
|
-
return false
|
|
116
|
-
}
|
|
117
88
|
}
|
|
118
89
|
|
|
119
90
|
async function ensureDir(p) {
|
|
@@ -290,12 +261,9 @@ export async function installService({ bin } = {}) {
|
|
|
290
261
|
const hubAgentBin = bin || detectHubAgentBin()
|
|
291
262
|
await ensureLogFile()
|
|
292
263
|
|
|
293
|
-
// agent.json の永続フラグを読み、テンプレ展開に渡す (再生成でも維持されるように)。
|
|
294
|
-
const chatShared = await readChatSharedFromConfig()
|
|
295
|
-
|
|
296
264
|
if (process.platform === "darwin") {
|
|
297
265
|
const tpl = await readTemplate("co.cocorograph.hub-agent.plist")
|
|
298
|
-
const expanded = expandTemplate(tpl, hubAgentBin
|
|
266
|
+
const expanded = expandTemplate(tpl, hubAgentBin)
|
|
299
267
|
const dest = macPlistPath()
|
|
300
268
|
await ensureDir(path.dirname(dest))
|
|
301
269
|
await fs.writeFile(dest, expanded, { mode: 0o644 })
|
|
@@ -317,7 +285,7 @@ export async function installService({ bin } = {}) {
|
|
|
317
285
|
|
|
318
286
|
if (process.platform === "linux") {
|
|
319
287
|
const tpl = await readTemplate("hub-agent.service")
|
|
320
|
-
const expanded = expandTemplate(tpl, hubAgentBin
|
|
288
|
+
const expanded = expandTemplate(tpl, hubAgentBin)
|
|
321
289
|
const dest = linuxUnitPath()
|
|
322
290
|
await ensureDir(path.dirname(dest))
|
|
323
291
|
await fs.writeFile(dest, expanded, { mode: 0o644 })
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<string>__PATH__</string>
|
|
53
53
|
<key>HOME</key>
|
|
54
54
|
<string>__HOME__</string>
|
|
55
|
-
|
|
55
|
+
__NODE_EXTRA_CA_CERTS_PLIST_ENTRY__ </dict>
|
|
56
56
|
|
|
57
57
|
<!-- KeepAlive で過剰再起動した時に 10 秒スロットルする -->
|
|
58
58
|
<key>ThrottleInterval</key>
|
|
@@ -12,7 +12,7 @@ Type=simple
|
|
|
12
12
|
# 行に、未 set なら空文字列に」展開する。
|
|
13
13
|
Environment=PATH=__PATH__
|
|
14
14
|
Environment=HOME=__HOME__
|
|
15
|
-
|
|
15
|
+
__NODE_EXTRA_CA_CERTS_SYSTEMD_LINE__WorkingDirectory=__HOME__
|
|
16
16
|
ExecStart=__HUB_AGENT_BIN__ start
|
|
17
17
|
Restart=always
|
|
18
18
|
RestartSec=10
|