@cocorograph/hub-agent 0.8.71 → 0.8.73
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/codex-stream-bridge.mjs +36 -31
- package/src/main.mjs +20 -5
package/package.json
CHANGED
|
@@ -980,6 +980,8 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
980
980
|
/** 取消成功 id は accepted(二重実行ガード)から外さず、browser が「accepted +キュー不在」
|
|
981
981
|
* のバブル昇格から除外するための別軸として保持する。 */
|
|
982
982
|
cancelledInputIds: [],
|
|
983
|
+
/** 取消が input より先着した場合の guard。後着 input で消費する。 */
|
|
984
|
+
canceledInputIds: new Set(),
|
|
983
985
|
/** @type {{resolve: Function, timer: any} | null} round3 major#2: turn/start 後に turn/started
|
|
984
986
|
* (activeTurnId 確定) を待つ barrier の待機者。timeout 付きでハングを防ぐ。 */
|
|
985
987
|
_turnActiveWaiter: null,
|
|
@@ -2166,16 +2168,34 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
2166
2168
|
/** 次ターン FIFO に残っている browser 入力を id 指定で取り消す。
|
|
2167
2169
|
* dispatchingInput は既に turn/start RPC 中なので対象外(実行開始済みとして扱う)。 */
|
|
2168
2170
|
cancelQueued({ stream_id, id }) {
|
|
2169
|
-
const session = this.
|
|
2170
|
-
if (!
|
|
2171
|
+
const session = this.sessions.get(stream_id)
|
|
2172
|
+
if (!session || session.dead) {
|
|
2173
|
+
return { outcome: "stream_missing", flush: () => {} }
|
|
2174
|
+
}
|
|
2175
|
+
if (typeof id !== "string" || !id) throw new Error("invalid_input_id")
|
|
2171
2176
|
const index = session.inputQueue.findIndex(
|
|
2172
2177
|
(item) =>
|
|
2173
2178
|
(item?.inputId === id || item?.deliveryId === id) &&
|
|
2174
2179
|
item !== session.dispatchingInput,
|
|
2175
2180
|
)
|
|
2176
2181
|
if (index < 0) {
|
|
2177
|
-
|
|
2178
|
-
|
|
2182
|
+
// accepted は二重実行防止の記録。取消済みなら再要求でも実行開始済みに変えない。
|
|
2183
|
+
if (session.cancelledInputIds?.includes(id)) {
|
|
2184
|
+
return { outcome: "cancelled", flush: () => this._emitQueueState(session) }
|
|
2185
|
+
}
|
|
2186
|
+
const dispatching = session.dispatchingInput
|
|
2187
|
+
const alreadyStarted =
|
|
2188
|
+
dispatching?.inputId === id ||
|
|
2189
|
+
dispatching?.deliveryId === id ||
|
|
2190
|
+
session.acceptedInputIds?.includes(id)
|
|
2191
|
+
if (!alreadyStarted) {
|
|
2192
|
+
if (session.canceledInputIds.size >= 1024) session.canceledInputIds.clear()
|
|
2193
|
+
session.canceledInputIds.add(id)
|
|
2194
|
+
}
|
|
2195
|
+
return {
|
|
2196
|
+
outcome: alreadyStarted ? "already_started" : "cancelled",
|
|
2197
|
+
flush: () => this._emitQueueState(session),
|
|
2198
|
+
}
|
|
2179
2199
|
}
|
|
2180
2200
|
const [item] = session.inputQueue.splice(index, 1)
|
|
2181
2201
|
this._recordCancelledInput(session, item.inputId || item.deliveryId)
|
|
@@ -2185,8 +2205,8 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
2185
2205
|
})
|
|
2186
2206
|
item.reject(err)
|
|
2187
2207
|
}
|
|
2188
|
-
|
|
2189
|
-
return
|
|
2208
|
+
// main が outcome を送った後で snapshot を送る。
|
|
2209
|
+
return { outcome: "cancelled", flush: () => this._emitQueueState(session) }
|
|
2190
2210
|
}
|
|
2191
2211
|
|
|
2192
2212
|
_emitQueueState(session, startedItem = null) {
|
|
@@ -3573,6 +3593,12 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
3573
3593
|
this._touch(session, stream_id)
|
|
3574
3594
|
const input = userInputFromMessage(message)
|
|
3575
3595
|
const text = input.map((part) => part?.text || "").join("\n")
|
|
3596
|
+
if (inputId && session.canceledInputIds.delete(inputId)) {
|
|
3597
|
+
this._recordAcceptedInput(session, inputId)
|
|
3598
|
+
this._recordCancelledInput(session, inputId)
|
|
3599
|
+
this._emitQueueState(session)
|
|
3600
|
+
return { method: "queued", deliveryId }
|
|
3601
|
+
}
|
|
3576
3602
|
if (inputId && session.acceptedInputIds?.includes(inputId)) {
|
|
3577
3603
|
return { method: session.activeTurnId ? "queued" : "turn/start", deliveryId }
|
|
3578
3604
|
}
|
|
@@ -4036,11 +4062,10 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
4036
4062
|
* 誤配送は取り返しがつかないため、両者の判定を意図的に分けてある。
|
|
4037
4063
|
*
|
|
4038
4064
|
* ⚠️ context%(ドーナツ)は thread 固有の値なので「どのセッションから読んでも同じ」という
|
|
4039
|
-
* 旧 rate-limit 前提(先着 1 件で十分)は成り立たない。
|
|
4040
|
-
*
|
|
4041
|
-
*
|
|
4042
|
-
*
|
|
4043
|
-
* のを防ぐ)。
|
|
4065
|
+
* 旧 rate-limit 前提(先着 1 件で十分)は成り立たない。per-cwd の表示系が同じ thread を
|
|
4066
|
+
* 選ぶよう、この一意規則へ寄せる。「最新」は閲覧 keepalive で動く lastActivityMs ではなく
|
|
4067
|
+
* 会話ターン境界時刻 lastTurnAtMs を基準にする(見ているだけの古い会話が本命を追い越すのを
|
|
4068
|
+
* 防ぐ)。
|
|
4044
4069
|
*
|
|
4045
4070
|
* @param {string} cwd
|
|
4046
4071
|
* @returns {object|null} 権威セッション(無ければ null)
|
|
@@ -4061,26 +4086,6 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
4061
4086
|
return best
|
|
4062
4087
|
}
|
|
4063
4088
|
|
|
4064
|
-
/**
|
|
4065
|
-
* 指定 cwd に一致するライブ (attach 済み) codex セッションの threadId を返す。
|
|
4066
|
-
* Claude 版 `liveSessionIdForCwd` の対。
|
|
4067
|
-
*
|
|
4068
|
-
* ⚠️ 2026-08-02 時点で **本番経路からの呼び出し元は無い** (テストのみ)。旧利用元だった
|
|
4069
|
-
* terminal mirror は撤去済み。「main.mjs の state loop から使う」という記述も以前から
|
|
4070
|
-
* 実態と食い違っていた (startStateLoop に codexBridge は渡るが、usage.mjs 側で参照して
|
|
4071
|
-
* いない)。撤去可否は別途調査してから判断すること。
|
|
4072
|
-
*
|
|
4073
|
-
* ⚠️ T06407 増分2: 旧実装は「先に見つかったもの」を返していたが、これは 5h/7d(アカウント
|
|
4074
|
-
* 全体で代表 1 件で十分)前提だった。context% は thread 固有なので liveSessionForCwd の
|
|
4075
|
-
* 権威規則(最終活性が最新)で解決し、usage.response の per-cwd 集計と収束させる。
|
|
4076
|
-
*
|
|
4077
|
-
* @param {string} cwd
|
|
4078
|
-
* @returns {string|null}
|
|
4079
|
-
*/
|
|
4080
|
-
threadIdForCwd(cwd) {
|
|
4081
|
-
return this.liveSessionForCwd(cwd)?.threadId ?? null
|
|
4082
|
-
}
|
|
4083
|
-
|
|
4084
4089
|
/**
|
|
4085
4090
|
* 指定 cwd でライブなセッションへメッセージを直接注入する (sibling-inject 用、
|
|
4086
4091
|
* 2026-07-04)。Claude 版 `sendToCwd` の対。
|
package/src/main.mjs
CHANGED
|
@@ -1352,9 +1352,7 @@ export async function startDaemon({ version, claudeSdk } = {}) {
|
|
|
1352
1352
|
logger,
|
|
1353
1353
|
intervalMs: 5_000,
|
|
1354
1354
|
claudeBridge,
|
|
1355
|
-
//
|
|
1356
|
-
// rollout ベースで解決するために渡す (main.mjs startStateLoop 内の codexBridge.threadIdForCwd
|
|
1357
|
-
// 参照)。
|
|
1355
|
+
// 背景 Codex チャットセッションの状態を state loop へ渡す。
|
|
1358
1356
|
codexBridge,
|
|
1359
1357
|
readinessTracker,
|
|
1360
1358
|
// tmux session_name → bind 中の session_id。turn_active を「そのペインが実際に走らせている
|
|
@@ -2331,6 +2329,8 @@ export function buildCodexReadyPayload(streamId, info, queueState) {
|
|
|
2331
2329
|
// Browser は旧 hub-agent との混在期間、この明示 capability が true のときだけ
|
|
2332
2330
|
// 子 thread の個別停止 UI を有効化する。省略は未対応を意味する。
|
|
2333
2331
|
supports_subagent_interrupt: true,
|
|
2332
|
+
// 旧 agent との混在時、取消結果を待てることを明示する。
|
|
2333
|
+
supports_queue_cancel_response: true,
|
|
2334
2334
|
}
|
|
2335
2335
|
if (Number.isInteger(info?.historyStart)) payload.history_start = info.historyStart
|
|
2336
2336
|
if (Number.isInteger(info?.historyTotal)) payload.history_total = info.historyTotal
|
|
@@ -3333,6 +3333,9 @@ export function handleClaudeChatInput(msg, ctx) {
|
|
|
3333
3333
|
/**
|
|
3334
3334
|
* `tmux.exec` を処理し、`tmux.result` を返す。
|
|
3335
3335
|
*
|
|
3336
|
+
* 公式 Hub の送信入口は撤去済みだが、公開 0.x package の root handler / deep import、
|
|
3337
|
+
* plugin hook、旧 Hub との互換入口として handler・dispatch・execTmux を維持する。
|
|
3338
|
+
*
|
|
3336
3339
|
* plugin の `interceptTmuxExec` hook が `{ command, args, env }` を返したときは、その
|
|
3337
3340
|
* command を代わりに spawn する (例: remote-ops プラグインで ssh 越しに実行)。hook が
|
|
3338
3341
|
* 何も返さなければ既定のローカル `execTmux` へ落ちる。どちらの経路でも
|
|
@@ -4797,9 +4800,16 @@ export async function dispatch(msg, ctx) {
|
|
|
4797
4800
|
// turn/steer / turn/interrupt は明示操作の別経路として扱う。
|
|
4798
4801
|
handleCodexChatInput(msg, ctx)
|
|
4799
4802
|
return
|
|
4800
|
-
case "codex.queue.cancel":
|
|
4803
|
+
case "codex.queue.cancel": {
|
|
4804
|
+
const cancelRes = {
|
|
4805
|
+
type: "codex.queue.response",
|
|
4806
|
+
stream_id: msg.stream_id,
|
|
4807
|
+
request_id: msg.request_id || null,
|
|
4808
|
+
input_id: msg.id || null,
|
|
4809
|
+
}
|
|
4810
|
+
let canceled
|
|
4801
4811
|
try {
|
|
4802
|
-
ctx.codexBridge?.cancelQueued({
|
|
4812
|
+
canceled = ctx.codexBridge?.cancelQueued({
|
|
4803
4813
|
stream_id: msg.stream_id,
|
|
4804
4814
|
id: msg.id,
|
|
4805
4815
|
})
|
|
@@ -4808,8 +4818,13 @@ export async function dispatch(msg, ctx) {
|
|
|
4808
4818
|
{ err: err?.message, stream_id: msg.stream_id, input_id: msg.id },
|
|
4809
4819
|
"codex queued input cancel failed",
|
|
4810
4820
|
)
|
|
4821
|
+
ctx.client.send({ ...cancelRes, error: err?.message || "cancel_failed" })
|
|
4822
|
+
return
|
|
4811
4823
|
}
|
|
4824
|
+
ctx.client.send({ ...cancelRes, outcome: canceled?.outcome || "stream_missing" })
|
|
4825
|
+
canceled?.flush?.()
|
|
4812
4826
|
return
|
|
4827
|
+
}
|
|
4813
4828
|
case "codex.permission.reply":
|
|
4814
4829
|
if (!ctx.codexBridge) return
|
|
4815
4830
|
ctx.codexBridge.permissionReply({
|