@cocorograph/hub-agent 0.8.116 → 0.8.118
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 +3 -24
- package/src/codex-stream-bridge.mjs +4 -19
- package/src/grok-stream-bridge.mjs +6 -12
- package/src/main.mjs +69 -100
- package/src/session-restore.mjs +9 -7
- package/src/sibling-inject.mjs +14 -0
- package/src/sibling-token.mjs +35 -0
package/package.json
CHANGED
|
@@ -34,35 +34,14 @@ import {
|
|
|
34
34
|
} from "./tool-result-mask.mjs"
|
|
35
35
|
import { createClaudeDisplayMasker, claudeMaskFailure } from "./claude-display-mask.mjs"
|
|
36
36
|
import { copyPermissionInput, preparePermissionInput, restorePermissionInput, PERMISSION_MASK_DENIAL } from "./permission-display-mask.mjs"
|
|
37
|
-
import {
|
|
37
|
+
import { buildSiblingEnv } from "./sibling-token.mjs"
|
|
38
38
|
import { recordInjectSourcePersistent } from "./inject-source-store.mjs"
|
|
39
39
|
import { readPositiveIntEnv, sanitizePositiveInt } from "./positive-int-env.mjs"
|
|
40
40
|
import { nextSessionSeq } from "./session-seq.mjs"
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* sibling-inject (2026-07-04) 用: spawn する claude プロセス自身の環境変数に、
|
|
44
|
-
* この cwd の capability トークンを乗せる。ディスクには書かず、プロセス環境変数
|
|
45
|
-
* としてのみ渡す (src/sibling-token.mjs のコメント参照)。
|
|
46
|
-
* @param {string} cwd
|
|
47
|
-
* @returns {NodeJS.ProcessEnv & {COCKPIT_SIBLING_TOKEN: string, COCKPIT_SIBLING_CWD: string,
|
|
48
|
-
* COCKPIT_DELEGATE_EVENT_ACK: string}}
|
|
49
|
-
*/
|
|
42
|
+
/** sibling-inject 用の spawn 環境 (共通処理は sibling-token.mjs の buildSiblingEnv)。 */
|
|
50
43
|
function siblingEnvFor(cwd) {
|
|
51
|
-
return {
|
|
52
|
-
...process.env,
|
|
53
|
-
COCKPIT_SIBLING_TOKEN: getOrCreateSiblingToken(cwd),
|
|
54
|
-
COCKPIT_SIBLING_CWD: cwd,
|
|
55
|
-
// Spawn-time display capability; rejection by an older daemon does not stop the review.
|
|
56
|
-
COCKPIT_REVIEW_EVENTS: "1",
|
|
57
|
-
// hub-agent が spawn した Claude 配下 producer だけが delegate-event の受理 ACK を
|
|
58
|
-
// 要求するための capability。能力は spawn 時点で固定されるため、hub-agent を旧版へ
|
|
59
|
-
// ロールバックしても既存 producer は ACK を要求し続け、timeout し得る。
|
|
60
|
-
// 旧 bundle はこの値を無視し、従来どおり fire-and-forget のまま動く。
|
|
61
|
-
COCKPIT_DELEGATE_EVENT_ACK: "1",
|
|
62
|
-
// ask-sibling スキル (cockpit_orch.py) が「自分は claude/codex どちらか」を
|
|
63
|
-
// 環境変数から確実に判定できるようにする (ヒューリスティック不要)。
|
|
64
|
-
COCKPIT_SIBLING_SELF_KIND: "claude",
|
|
65
|
-
}
|
|
44
|
+
return buildSiblingEnv(cwd, { selfKind: "claude", delegateEventAck: true })
|
|
66
45
|
}
|
|
67
46
|
|
|
68
47
|
/** browser 切断後、セッションを生かしたまま再接続を待つ idle TTL (ミリ秒)。これを過ぎ、
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
snapshotCodexDeliveryAudit,
|
|
69
69
|
verifyCodexDeliveryAudit,
|
|
70
70
|
} from "./codex-delivery-audit.mjs"
|
|
71
|
-
import {
|
|
71
|
+
import { buildSiblingEnv } from "./sibling-token.mjs"
|
|
72
72
|
import { readPositiveIntEnv, sanitizePositiveInt } from "./positive-int-env.mjs"
|
|
73
73
|
import { nextSessionSeq } from "./session-seq.mjs"
|
|
74
74
|
import { codexRolloutPresence, listCodexThreadsFromDisk } from "./usage.mjs"
|
|
@@ -192,25 +192,10 @@ async function buildCodexHostCapabilities({
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
/**
|
|
196
|
-
*
|
|
197
|
-
* 環境変数に、この cwd の capability トークンを乗せる。ディスクには書かず、
|
|
198
|
-
* プロセス環境変数としてのみ渡す (src/sibling-token.mjs のコメント参照、
|
|
199
|
-
* claude-stream-bridge.mjs の同名ヘルパーと対)。
|
|
200
|
-
* @param {string} cwd
|
|
201
|
-
* @returns {NodeJS.ProcessEnv}
|
|
202
|
-
*/
|
|
195
|
+
/** sibling-inject 用の spawn 環境 (共通処理は sibling-token.mjs の buildSiblingEnv)。
|
|
196
|
+
* Codex には delegate-event の ACK capability を付けない。 */
|
|
203
197
|
function siblingEnvFor(cwd) {
|
|
204
|
-
return {
|
|
205
|
-
...process.env,
|
|
206
|
-
COCKPIT_SIBLING_TOKEN: getOrCreateSiblingToken(cwd),
|
|
207
|
-
COCKPIT_SIBLING_CWD: cwd,
|
|
208
|
-
// Spawn-time display capability; rejection by an older daemon does not stop the review.
|
|
209
|
-
COCKPIT_REVIEW_EVENTS: "1",
|
|
210
|
-
// ask-sibling スキル (cockpit_orch.py) が「自分は claude/codex どちらか」を
|
|
211
|
-
// 環境変数から確実に判定できるようにする (ヒューリスティック不要)。
|
|
212
|
-
COCKPIT_SIBLING_SELF_KIND: "codex",
|
|
213
|
-
}
|
|
198
|
+
return buildSiblingEnv(cwd, { selfKind: "codex" })
|
|
214
199
|
}
|
|
215
200
|
|
|
216
201
|
// ── Stage 5 (2026-07-19): bounded warm pool + 長 idle TTL への再設計 ───────────
|
|
@@ -3,23 +3,13 @@ import { EventEmitter } from 'node:events'
|
|
|
3
3
|
import { readPositiveIntEnv, sanitizePositiveInt } from './positive-int-env.mjs'
|
|
4
4
|
import { GrokAcpClient } from './grok-acp-client.mjs'
|
|
5
5
|
import { isUnderRemoval } from './chat-removal-guard.mjs'
|
|
6
|
-
import {
|
|
6
|
+
import { buildSiblingEnv } from './sibling-token.mjs'
|
|
7
7
|
|
|
8
8
|
// hub-bundle.mjs の HUB_MCP_NAMES と揃える。重い依存を避け、ここで定義する。
|
|
9
9
|
const HUB_MCP_NAMES = new Set(['hub', 'hub_staff'])
|
|
10
10
|
|
|
11
11
|
function siblingEnvFor(cwd, inherited = process.env) {
|
|
12
|
-
|
|
13
|
-
...inherited,
|
|
14
|
-
COCKPIT_SIBLING_TOKEN: getOrCreateSiblingToken(cwd),
|
|
15
|
-
COCKPIT_SIBLING_CWD: cwd,
|
|
16
|
-
COCKPIT_REVIEW_EVENTS: '1',
|
|
17
|
-
COCKPIT_DELEGATE_EVENT_ACK: '1',
|
|
18
|
-
COCKPIT_SIBLING_SELF_KIND: 'grok',
|
|
19
|
-
}
|
|
20
|
-
delete env.CLAUDE_CODE_SESSION_ID
|
|
21
|
-
delete env.CODEX_THREAD_ID
|
|
22
|
-
return env
|
|
12
|
+
return buildSiblingEnv(cwd, { selfKind: 'grok', delegateEventAck: true, stripParentSessionIds: true, inherited })
|
|
23
13
|
}
|
|
24
14
|
|
|
25
15
|
// 完了済み配送 ID の保持上限。上限を超えた古い完了済み ID の再送は再実行されうる。
|
|
@@ -489,6 +479,10 @@ export class GrokStreamBridge extends EventEmitter {
|
|
|
489
479
|
session.cancelledTurn = session.turnSeq
|
|
490
480
|
this._clearTurnPermissions(session, session.turnSeq, true)
|
|
491
481
|
session.client.notify('session/cancel', { sessionId: session.sessionId })
|
|
482
|
+
if (!session.promptInFlight && !session.dispatchingInput) {
|
|
483
|
+
this.emit('grok_turn_complete', { stream_id: session.stream_id, session_id: session.sessionId,
|
|
484
|
+
stopReason: 'cancelled', lastSeq: session.updateSeq })
|
|
485
|
+
}
|
|
492
486
|
}
|
|
493
487
|
|
|
494
488
|
permissionReply({ stream_id, request_id, result }) {
|
package/src/main.mjs
CHANGED
|
@@ -3722,6 +3722,61 @@ export function createClaudeAttachTiming(ctx, msg) {
|
|
|
3722
3722
|
}
|
|
3723
3723
|
}
|
|
3724
3724
|
|
|
3725
|
+
/**
|
|
3726
|
+
* Cockpit チャットの添付ファイル受信 (チャンク 1 件) を Claude / Codex / Grok で共有する。
|
|
3727
|
+
* CLI ごとに違うのは返信型と既定 cwd だけ。既定 cwd は uploadManager がある場合に限り、
|
|
3728
|
+
* msg.cwd が無いときだけ評価する。完了チャンクだけに done を返し、継続中チャンクは応答しない
|
|
3729
|
+
* (browser は自分のチャンク送出完了で進捗を把握する)。
|
|
3730
|
+
* @param {object} msg
|
|
3731
|
+
* @param {object} ctx
|
|
3732
|
+
* @param {string} doneType
|
|
3733
|
+
* @param {() => string | undefined} getDefaultCwd
|
|
3734
|
+
*/
|
|
3735
|
+
async function handleChatUpload(msg, ctx, doneType, getDefaultCwd) {
|
|
3736
|
+
if (!ctx.uploadManager) {
|
|
3737
|
+
ctx.client.send({
|
|
3738
|
+
type: doneType,
|
|
3739
|
+
stream_id: msg.stream_id,
|
|
3740
|
+
upload_id: msg.upload_id,
|
|
3741
|
+
ok: false,
|
|
3742
|
+
error: "upload_unavailable",
|
|
3743
|
+
})
|
|
3744
|
+
return
|
|
3745
|
+
}
|
|
3746
|
+
try {
|
|
3747
|
+
const res = await ctx.uploadManager.addChunk({
|
|
3748
|
+
upload_id: msg.upload_id,
|
|
3749
|
+
filename: msg.filename,
|
|
3750
|
+
media_type: msg.media_type,
|
|
3751
|
+
total_size: msg.total_size,
|
|
3752
|
+
total_chunks: msg.total_chunks,
|
|
3753
|
+
chunk_index: msg.chunk_index,
|
|
3754
|
+
data: msg.data,
|
|
3755
|
+
cwd: msg.cwd || getDefaultCwd(),
|
|
3756
|
+
})
|
|
3757
|
+
if (res.done) {
|
|
3758
|
+
ctx.client.send({
|
|
3759
|
+
type: doneType,
|
|
3760
|
+
stream_id: msg.stream_id,
|
|
3761
|
+
upload_id: msg.upload_id,
|
|
3762
|
+
ok: true,
|
|
3763
|
+
path: res.path,
|
|
3764
|
+
filename: res.filename,
|
|
3765
|
+
media_type: res.media_type,
|
|
3766
|
+
size: res.size,
|
|
3767
|
+
})
|
|
3768
|
+
}
|
|
3769
|
+
} catch (err) {
|
|
3770
|
+
ctx.client.send({
|
|
3771
|
+
type: doneType,
|
|
3772
|
+
stream_id: msg.stream_id,
|
|
3773
|
+
upload_id: msg.upload_id,
|
|
3774
|
+
ok: false,
|
|
3775
|
+
error: err?.message || String(err),
|
|
3776
|
+
})
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3725
3780
|
/**
|
|
3726
3781
|
* DispatchScheduler から呼ばれる本番メッセージ入口。
|
|
3727
3782
|
* export はハンドラ契約を subprocess なしで検証するテスト専用の公開面でもある。
|
|
@@ -3739,10 +3794,11 @@ export async function dispatch(msg, ctx) {
|
|
|
3739
3794
|
runInLane: scheduler && ((lane, fn) => scheduler.runInLane(lane, fn)),
|
|
3740
3795
|
claudeBridge: ctx.claudeBridge,
|
|
3741
3796
|
codexBridge: ctx.codexBridge,
|
|
3797
|
+
grokBridge: ctx.grokBridge,
|
|
3742
3798
|
claudeAttachOptions: () => resolveClaudeAttachOptions(ctx.config),
|
|
3743
3799
|
available: (kind) => kind === "codex"
|
|
3744
3800
|
? codexAvailableForDispatch(ctx)
|
|
3745
|
-
: ctx.availableClis?.includes(
|
|
3801
|
+
: ctx.availableClis?.includes(kind) === true,
|
|
3746
3802
|
logger: ctx.logger,
|
|
3747
3803
|
})
|
|
3748
3804
|
return
|
|
@@ -3945,56 +4001,12 @@ export async function dispatch(msg, ctx) {
|
|
|
3945
4001
|
// message: { role: 'user', content: string | Array } を期待
|
|
3946
4002
|
handleClaudeChatInput(msg, ctx)
|
|
3947
4003
|
return
|
|
3948
|
-
case "claude.upload":
|
|
4004
|
+
case "claude.upload":
|
|
3949
4005
|
// Cockpit チャットモードの添付ファイル受信 (チャンク 1 件)。全チャンク到着で
|
|
3950
4006
|
// cwd 配下の .cockpit-uploads/<日付>/ に保存し、保存パスを browser へ返す。
|
|
3951
4007
|
// browser はそのパスを claude.input 本文に載せ、Claude が Read ツールで読む。
|
|
3952
|
-
|
|
3953
|
-
ctx.client.send({
|
|
3954
|
-
type: "claude.upload.done",
|
|
3955
|
-
stream_id: msg.stream_id,
|
|
3956
|
-
upload_id: msg.upload_id,
|
|
3957
|
-
ok: false,
|
|
3958
|
-
error: "upload_unavailable",
|
|
3959
|
-
})
|
|
3960
|
-
return
|
|
3961
|
-
}
|
|
3962
|
-
try {
|
|
3963
|
-
const res = await ctx.uploadManager.addChunk({
|
|
3964
|
-
upload_id: msg.upload_id,
|
|
3965
|
-
filename: msg.filename,
|
|
3966
|
-
media_type: msg.media_type,
|
|
3967
|
-
total_size: msg.total_size,
|
|
3968
|
-
total_chunks: msg.total_chunks,
|
|
3969
|
-
chunk_index: msg.chunk_index,
|
|
3970
|
-
data: msg.data,
|
|
3971
|
-
cwd: msg.cwd || ctx.config?.claude_cwd || undefined,
|
|
3972
|
-
})
|
|
3973
|
-
if (res.done) {
|
|
3974
|
-
// 完了チャンクのみ done を返す。継続中チャンクは応答しない
|
|
3975
|
-
// (browser は自分のチャンク送出完了で進捗を把握する)。
|
|
3976
|
-
ctx.client.send({
|
|
3977
|
-
type: "claude.upload.done",
|
|
3978
|
-
stream_id: msg.stream_id,
|
|
3979
|
-
upload_id: msg.upload_id,
|
|
3980
|
-
ok: true,
|
|
3981
|
-
path: res.path,
|
|
3982
|
-
filename: res.filename,
|
|
3983
|
-
media_type: res.media_type,
|
|
3984
|
-
size: res.size,
|
|
3985
|
-
})
|
|
3986
|
-
}
|
|
3987
|
-
} catch (err) {
|
|
3988
|
-
ctx.client.send({
|
|
3989
|
-
type: "claude.upload.done",
|
|
3990
|
-
stream_id: msg.stream_id,
|
|
3991
|
-
upload_id: msg.upload_id,
|
|
3992
|
-
ok: false,
|
|
3993
|
-
error: err?.message || String(err),
|
|
3994
|
-
})
|
|
3995
|
-
}
|
|
4008
|
+
await handleChatUpload(msg, ctx, "claude.upload.done", () => ctx.config?.claude_cwd || undefined)
|
|
3996
4009
|
return
|
|
3997
|
-
}
|
|
3998
4010
|
case "claude.permission.reply": {
|
|
3999
4011
|
if (!ctx.claudeBridge) return
|
|
4000
4012
|
ctx.claudeBridge.permissionReply({
|
|
@@ -4335,59 +4347,11 @@ export async function dispatch(msg, ctx) {
|
|
|
4335
4347
|
return
|
|
4336
4348
|
}
|
|
4337
4349
|
case "grok.upload":
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
ctx.client.send({
|
|
4343
|
-
type: doneType,
|
|
4344
|
-
stream_id: msg.stream_id,
|
|
4345
|
-
upload_id: msg.upload_id,
|
|
4346
|
-
ok: false,
|
|
4347
|
-
error: "upload_unavailable",
|
|
4348
|
-
})
|
|
4349
|
-
return
|
|
4350
|
-
}
|
|
4351
|
-
try {
|
|
4352
|
-
const res = await ctx.uploadManager.addChunk({
|
|
4353
|
-
upload_id: msg.upload_id,
|
|
4354
|
-
filename: msg.filename,
|
|
4355
|
-
media_type: msg.media_type,
|
|
4356
|
-
total_size: msg.total_size,
|
|
4357
|
-
total_chunks: msg.total_chunks,
|
|
4358
|
-
chunk_index: msg.chunk_index,
|
|
4359
|
-
data: msg.data,
|
|
4360
|
-
cwd: msg.cwd || getDefaultCwd(),
|
|
4361
|
-
})
|
|
4362
|
-
if (res.done) {
|
|
4363
|
-
ctx.client.send({
|
|
4364
|
-
type: doneType,
|
|
4365
|
-
stream_id: msg.stream_id,
|
|
4366
|
-
upload_id: msg.upload_id,
|
|
4367
|
-
ok: true,
|
|
4368
|
-
path: res.path,
|
|
4369
|
-
filename: res.filename,
|
|
4370
|
-
media_type: res.media_type,
|
|
4371
|
-
size: res.size,
|
|
4372
|
-
})
|
|
4373
|
-
}
|
|
4374
|
-
} catch (err) {
|
|
4375
|
-
ctx.client.send({
|
|
4376
|
-
type: doneType,
|
|
4377
|
-
stream_id: msg.stream_id,
|
|
4378
|
-
upload_id: msg.upload_id,
|
|
4379
|
-
ok: false,
|
|
4380
|
-
error: err?.message || String(err),
|
|
4381
|
-
})
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
if (msg.type === "grok.upload") {
|
|
4385
|
-
await handleUpload("grok.upload.done", () => defaultLaneCwd(ctx.config, "grok"))
|
|
4386
|
-
} else {
|
|
4387
|
-
await handleUpload("codex.upload.done", () => ctx.config?.claude_cwd || undefined)
|
|
4388
|
-
}
|
|
4350
|
+
await handleChatUpload(msg, ctx, "grok.upload.done", () => defaultLaneCwd(ctx.config, "grok"))
|
|
4351
|
+
return
|
|
4352
|
+
case "codex.upload":
|
|
4353
|
+
await handleChatUpload(msg, ctx, "codex.upload.done", () => ctx.config?.claude_cwd || undefined)
|
|
4389
4354
|
return
|
|
4390
|
-
}
|
|
4391
4355
|
case "codex.input":
|
|
4392
4356
|
// 通常送信は idle なら turn/start、busy なら hub-agent の次ターン FIFO。
|
|
4393
4357
|
// turn/steer / turn/interrupt は明示操作の別経路として扱う。
|
|
@@ -5027,6 +4991,7 @@ export async function dispatch(msg, ctx) {
|
|
|
5027
4991
|
noteRestoreStop(ctx.sessionRestoreState, "claude", cwd)
|
|
5028
4992
|
result.stopped = await ctx.claudeBridge.stopSessionsFor({ cwd, reason: "chat-stop" })
|
|
5029
4993
|
} else if (msg.cli_kind === "grok") {
|
|
4994
|
+
noteRestoreStop(ctx.sessionRestoreState, "grok", cwd)
|
|
5030
4995
|
result.stopped = await ctx.grokBridge?.stopSessionsForCwd({ cwd, reason: "chat-stop" }) ?? 0
|
|
5031
4996
|
} else {
|
|
5032
4997
|
noteRestoreStop(ctx.sessionRestoreState, "codex", cwd)
|
|
@@ -5047,7 +5012,11 @@ export async function dispatch(msg, ctx) {
|
|
|
5047
5012
|
}
|
|
5048
5013
|
} else if (msg.cli_kind === "grok") {
|
|
5049
5014
|
if (typeof msg.stream_id !== "string" || !msg.stream_id) result.error = "stream_id required"
|
|
5050
|
-
else
|
|
5015
|
+
else {
|
|
5016
|
+
const streamCwd = ctx.grokBridge?.sessions?.get?.(msg.stream_id)?.cwd
|
|
5017
|
+
if (streamCwd) noteRestoreStop(ctx.sessionRestoreState, "grok", streamCwd)
|
|
5018
|
+
result.stopped = await ctx.grokBridge?.stopSessionForStream({ stream_id: msg.stream_id, reason: "chat-stop" }) ?? 0
|
|
5019
|
+
}
|
|
5051
5020
|
} else if (msg.cli_kind === "codex") {
|
|
5052
5021
|
if (typeof msg.stream_id !== "string" || !msg.stream_id) result.error = "stream_id required"
|
|
5053
5022
|
else {
|
package/src/session-restore.mjs
CHANGED
|
@@ -31,7 +31,7 @@ export function noteRestoreStop(state, cliKind, cwd) {
|
|
|
31
31
|
* 受付状態と Hub の「接続ごとに 1 回」の送信がかみ合わず、復元が途中で止まる (2 巡目 blocker)。
|
|
32
32
|
*/
|
|
33
33
|
export async function restoreSessions(message, {
|
|
34
|
-
state, claudeBridge, codexBridge, available, logger,
|
|
34
|
+
state, claudeBridge, codexBridge, grokBridge, available, logger,
|
|
35
35
|
runInLane = (_lane, fn) => fn(), statCwd = stat,
|
|
36
36
|
claudeAttachOptions = () => ({}),
|
|
37
37
|
}) {
|
|
@@ -40,7 +40,7 @@ export async function restoreSessions(message, {
|
|
|
40
40
|
return
|
|
41
41
|
}
|
|
42
42
|
state.accepted = true
|
|
43
|
-
const bridges = { claude: claudeBridge, codex: codexBridge }
|
|
43
|
+
const bridges = { claude: claudeBridge, codex: codexBridge, grok: grokBridge }
|
|
44
44
|
const logSkip = (row, reason) => logger?.info?.({
|
|
45
45
|
cli_kind: row?.cli_kind, cwd: row?.cwd, session_id: row?.session_id, reason,
|
|
46
46
|
}, "agent.sessions.restore skipped")
|
|
@@ -48,7 +48,7 @@ export async function restoreSessions(message, {
|
|
|
48
48
|
logSkip(null, "sessions must be an array")
|
|
49
49
|
return
|
|
50
50
|
}
|
|
51
|
-
const snapshot = () => describeLiveSessionsUnion({ claudeBridge, codexBridge }).map((s) => ({
|
|
51
|
+
const snapshot = () => describeLiveSessionsUnion({ claudeBridge, codexBridge, grokBridge }).map((s) => ({
|
|
52
52
|
cli_kind: s.kind, cwd: s.cwd, session_id: s.session_id || s.thread_id,
|
|
53
53
|
}))
|
|
54
54
|
const same = (a, b) => a.cli_kind === b.cli_kind && a.cwd === b.cwd && a.session_id === b.session_id
|
|
@@ -73,7 +73,7 @@ export async function restoreSessions(message, {
|
|
|
73
73
|
const live = [...current, ...selected.filter((r) => !current.some((s) => same(s, r)))]
|
|
74
74
|
if (live.length >= WARM_TOTAL_MAX) break
|
|
75
75
|
try {
|
|
76
|
-
if (!row || !["claude", "codex"].includes(row.cli_kind)) throw new Error("unknown CLI")
|
|
76
|
+
if (!row || !["claude", "codex", "grok"].includes(row.cli_kind)) throw new Error("unknown CLI")
|
|
77
77
|
if (!bridges[row.cli_kind] || !available(row.cli_kind)) throw new Error("CLI unavailable")
|
|
78
78
|
if (typeof row.session_id !== "string" || !row.session_id.trim()) throw new Error("empty session_id")
|
|
79
79
|
if (typeof row.cwd !== "string" || !path.isAbsolute(row.cwd)) throw new Error("cwd must be absolute")
|
|
@@ -115,9 +115,11 @@ export async function restoreSessions(message, {
|
|
|
115
115
|
// ここで欠けると復元した会話だけ権限モードが既定に落ちる (0.8.91 の実測)。
|
|
116
116
|
...(row.cli_kind === "claude"
|
|
117
117
|
? { ...claudeAttachOptions(), resumeSessionId: row.session_id }
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
: row.cli_kind === "grok"
|
|
119
|
+
? { resumeSessionId: row.session_id }
|
|
120
|
+
// Codex は承認ポリシー / サンドボックスを thread 開始時にしか受け取らない。Hub が
|
|
121
|
+
// Cockpit の既定を行に載せる (空なら載せない = config.toml 既定。通常 attach と同じ)。
|
|
122
|
+
: {
|
|
121
123
|
resumeThreadId: row.session_id,
|
|
122
124
|
approvalPolicy: nonEmpty(row.approval_policy),
|
|
123
125
|
sandbox: nonEmpty(row.sandbox),
|
package/src/sibling-inject.mjs
CHANGED
|
@@ -425,6 +425,16 @@ function queuedResult(r) {
|
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
// 配送前に未受理と確定した busy だけに再試行印を付ける。
|
|
429
|
+
function busyResult() {
|
|
430
|
+
return {
|
|
431
|
+
ok: false,
|
|
432
|
+
error: "target_busy",
|
|
433
|
+
retryable: true,
|
|
434
|
+
hint: "宛先の未完了配送が上限に達しているため、このメッセージは受理されていません。混雑が落ち着いてから再送してください。",
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
428
438
|
export class SiblingInjectBridge {
|
|
429
439
|
/**
|
|
430
440
|
* @param {{dir?: string, claudeBridge?: object, codexBridge?: object, grokBridge?: object, logger?: object,
|
|
@@ -1099,6 +1109,8 @@ export class SiblingInjectBridge {
|
|
|
1099
1109
|
}
|
|
1100
1110
|
} else if (r.status === "delivery_queued") {
|
|
1101
1111
|
result = queuedResult(r)
|
|
1112
|
+
} else if (r.status === "busy") {
|
|
1113
|
+
result = busyResult()
|
|
1102
1114
|
} else if (r.status === "delivery_unconfirmed" || r.status === "delivery_failed") {
|
|
1103
1115
|
result = {
|
|
1104
1116
|
ok: false,
|
|
@@ -1195,6 +1207,8 @@ export class SiblingInjectBridge {
|
|
|
1195
1207
|
}
|
|
1196
1208
|
} else if (r.status === "delivery_queued") {
|
|
1197
1209
|
result = queuedResult(r)
|
|
1210
|
+
} else if (r.status === "busy") {
|
|
1211
|
+
result = busyResult()
|
|
1198
1212
|
} else if (r.status === "delivery_unconfirmed" || r.status === "delivery_failed") {
|
|
1199
1213
|
result = {
|
|
1200
1214
|
ok: false,
|
package/src/sibling-token.mjs
CHANGED
|
@@ -100,6 +100,41 @@ export function siblingTokenCwd(token) {
|
|
|
100
100
|
return null
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* spawn する CLI プロセス自身の環境変数に、この cwd の sibling 連携用 capability を乗せる。
|
|
105
|
+
* ディスクには書かず、プロセス環境変数としてのみ渡す。継承元の環境オブジェクトは変更しない。
|
|
106
|
+
* @param {string} cwd
|
|
107
|
+
* @param {{ selfKind: "claude" | "codex" | "grok", delegateEventAck?: boolean,
|
|
108
|
+
* stripParentSessionIds?: boolean, inherited?: NodeJS.ProcessEnv }} options
|
|
109
|
+
* - selfKind: ask-sibling スキル (cockpit_orch.py) が「自分はどの CLI か」を環境変数から
|
|
110
|
+
* 確実に判定するための値。
|
|
111
|
+
* - delegateEventAck: hub-agent が spawn した producer だけが delegate-event の受理 ACK を
|
|
112
|
+
* 要求するための capability。spawn 時点で固定されるため、hub-agent を旧版へロールバック
|
|
113
|
+
* しても既存 producer は ACK を要求し続け、timeout し得る。旧 bundle はこの値を無視する。
|
|
114
|
+
* Codex には付けない。
|
|
115
|
+
* - stripParentSessionIds: 親 CLI のセッション ID を子へ継承させない (Grok)。
|
|
116
|
+
* @returns {NodeJS.ProcessEnv}
|
|
117
|
+
*/
|
|
118
|
+
export function buildSiblingEnv(cwd, { selfKind, delegateEventAck = false, stripParentSessionIds = false,
|
|
119
|
+
inherited = process.env }) {
|
|
120
|
+
const env = {
|
|
121
|
+
...inherited,
|
|
122
|
+
COCKPIT_SIBLING_TOKEN: getOrCreateSiblingToken(cwd),
|
|
123
|
+
COCKPIT_SIBLING_CWD: cwd,
|
|
124
|
+
// Spawn-time display capability; rejection by an older daemon does not stop the review.
|
|
125
|
+
COCKPIT_REVIEW_EVENTS: "1",
|
|
126
|
+
...(delegateEventAck ? { COCKPIT_DELEGATE_EVENT_ACK: "1" } : {}),
|
|
127
|
+
COCKPIT_SIBLING_SELF_KIND: selfKind,
|
|
128
|
+
}
|
|
129
|
+
// ACK を付けない CLI では、継承元に ACK があっても子へ持ち込まない。
|
|
130
|
+
if (!delegateEventAck) delete env.COCKPIT_DELEGATE_EVENT_ACK
|
|
131
|
+
if (stripParentSessionIds) {
|
|
132
|
+
delete env.CLAUDE_CODE_SESSION_ID
|
|
133
|
+
delete env.CODEX_THREAD_ID
|
|
134
|
+
}
|
|
135
|
+
return env
|
|
136
|
+
}
|
|
137
|
+
|
|
103
138
|
/** テスト用: トークンストアを全クリアする。 */
|
|
104
139
|
export function _resetSiblingTokens() {
|
|
105
140
|
tokensByCwd.clear()
|