@cocorograph/hub-agent 0.8.104 → 0.8.106
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 +17 -7
- package/src/codex-appserver-client.mjs +53 -16
- package/src/main.mjs +3 -1
package/package.json
CHANGED
|
@@ -966,8 +966,15 @@ export class ClaudeStreamSession {
|
|
|
966
966
|
this.logger?.debug({ stream_id: this.stream_id, cwd: this.cwd, stderr: text }, "claude cli stderr")
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
-
/**
|
|
970
|
-
|
|
969
|
+
/**
|
|
970
|
+
* SDK options.canUseTool: tool 起動を許可するかを browser に問い合わせる。
|
|
971
|
+
*
|
|
972
|
+
* SDK は第 3 引数 options.agentID に「サブエージェント内からの要求ならその ID」を渡す。
|
|
973
|
+
* これを載せないと、サブエージェントの承認要求がチーフ本人の要求と見分けられない
|
|
974
|
+
* (承認カードに依頼元が出ない)。チーフ本人の要求では agentID が無いので agent_id は省く。
|
|
975
|
+
* 「常に許可」の判定は browser 側でツール名単位のまま (依頼元で自動許可を止めない)。
|
|
976
|
+
*/
|
|
977
|
+
async _canUseTool(toolName, input, options) {
|
|
971
978
|
if (!this.onPermission) return { behavior: "allow", updatedInput: input }
|
|
972
979
|
let displayState
|
|
973
980
|
try {
|
|
@@ -976,15 +983,17 @@ export class ClaudeStreamSession {
|
|
|
976
983
|
return { behavior: "deny", message: PERMISSION_MASK_DENIAL }
|
|
977
984
|
}
|
|
978
985
|
const request_id = randomUUID()
|
|
986
|
+
const agent_id = typeof options?.agentID === "string" && options.agentID ? options.agentID : undefined
|
|
979
987
|
return await new Promise((resolve) => {
|
|
980
988
|
this._permissionResolvers.set(request_id, {
|
|
981
989
|
resolve,
|
|
982
990
|
tool_name: toolName,
|
|
983
991
|
input,
|
|
984
992
|
displayState,
|
|
993
|
+
agent_id,
|
|
985
994
|
})
|
|
986
995
|
try {
|
|
987
|
-
this.onPermission({ tool_name: toolName, input: displayState ? copyPermissionInput(displayState.displayInput) : input, request_id })
|
|
996
|
+
this.onPermission({ tool_name: toolName, input: displayState ? copyPermissionInput(displayState.displayInput) : input, request_id, ...(agent_id ? { agent_id } : {}) })
|
|
988
997
|
} catch (err) {
|
|
989
998
|
this.logger?.warn(
|
|
990
999
|
{ stream_id: this.stream_id },
|
|
@@ -1108,7 +1117,7 @@ export class ClaudeStreamSession {
|
|
|
1108
1117
|
if (!this.onPermission || this._permissionResolvers.size === 0) return
|
|
1109
1118
|
for (const [request_id, r] of this._permissionResolvers) {
|
|
1110
1119
|
try {
|
|
1111
|
-
this.onPermission({ tool_name: r.tool_name, input: r.displayState ? copyPermissionInput(r.displayState.displayInput) : r.input, request_id })
|
|
1120
|
+
this.onPermission({ tool_name: r.tool_name, input: r.displayState ? copyPermissionInput(r.displayState.displayInput) : r.input, request_id, ...(r.agent_id ? { agent_id: r.agent_id } : {}) })
|
|
1112
1121
|
} catch (err) {
|
|
1113
1122
|
this.logger?.warn(
|
|
1114
1123
|
{ stream_id: this.stream_id, request_id },
|
|
@@ -1504,7 +1513,7 @@ export class ClaudeStreamSession {
|
|
|
1504
1513
|
const options = {
|
|
1505
1514
|
cwd: this.cwd,
|
|
1506
1515
|
env: siblingEnvFor(this.cwd),
|
|
1507
|
-
canUseTool: (toolName, input) => this._canUseTool(toolName, input),
|
|
1516
|
+
canUseTool: (toolName, input, options) => this._canUseTool(toolName, input, options),
|
|
1508
1517
|
includePartialMessages: true,
|
|
1509
1518
|
includeHookEvents: true,
|
|
1510
1519
|
abortController: this._abortController,
|
|
@@ -2236,7 +2245,7 @@ export class ClaudeStreamSession {
|
|
|
2236
2245
|
const options = {
|
|
2237
2246
|
cwd: this.cwd,
|
|
2238
2247
|
env: siblingEnvFor(this.cwd),
|
|
2239
|
-
canUseTool: (toolName, input) => this._canUseTool(toolName, input),
|
|
2248
|
+
canUseTool: (toolName, input, options) => this._canUseTool(toolName, input, options),
|
|
2240
2249
|
includePartialMessages: true,
|
|
2241
2250
|
includeHookEvents: true,
|
|
2242
2251
|
abortController: this._residentAbortController,
|
|
@@ -3043,7 +3052,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3043
3052
|
this._liveBySession.set(key, session)
|
|
3044
3053
|
}
|
|
3045
3054
|
},
|
|
3046
|
-
onPermission: ({ tool_name, input, request_id }) => {
|
|
3055
|
+
onPermission: ({ tool_name, input, request_id, agent_id }) => {
|
|
3047
3056
|
// 多端末共有: session_id を載せて backend が session group へ broadcast できるように
|
|
3048
3057
|
// する (全端末に許可ダイアログを出し、先着 1 件の reply を採用する)。
|
|
3049
3058
|
this.emit("permission", {
|
|
@@ -3053,6 +3062,7 @@ export class ClaudeStreamBridge extends EventEmitter {
|
|
|
3053
3062
|
request_id,
|
|
3054
3063
|
tool_name,
|
|
3055
3064
|
input,
|
|
3065
|
+
...(agent_id ? { agent_id } : {}),
|
|
3056
3066
|
})
|
|
3057
3067
|
},
|
|
3058
3068
|
onPermissionCancel: ({ request_ids, reason }) => {
|
|
@@ -49,7 +49,9 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
49
49
|
this._requestTimeoutMs = resolveRequestTimeoutMs(requestTimeoutMs)
|
|
50
50
|
this.child = null
|
|
51
51
|
this.closed = false
|
|
52
|
-
|
|
52
|
+
// 改行待ちの受信断片 (Buffer のまま保持する)。_onStdout の説明を参照。
|
|
53
|
+
this._pendingChunks = []
|
|
54
|
+
this._pendingNeedsRescan = false
|
|
53
55
|
this._reqId = 0
|
|
54
56
|
this._pending = new Map()
|
|
55
57
|
}
|
|
@@ -143,24 +145,59 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
143
145
|
})
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
/**
|
|
149
|
+
* stdout の受信断片を改行区切りの JSON-RPC 行へ組み立てる。
|
|
150
|
+
*
|
|
151
|
+
* - 改行は**新しく届いた断片の中だけ**探す。以前は断片のたびに蓄積済み文字列全体を先頭から
|
|
152
|
+
* 探し直しており、thread/resume・thread/read の応答 (履歴全体を 1 行で返す。実測で最大 55MB)
|
|
153
|
+
* では断片数 × 蓄積量の二乗になって、55MB の応答 1 本に約 4.4 秒かかっていた (まとめて
|
|
154
|
+
* 解析すれば約 60ms)。
|
|
155
|
+
* - 文字列化は**行が揃ってから 1 回だけ**行う。断片ごとに `toString("utf8")` すると、断片の
|
|
156
|
+
* 境目にまたがった日本語等のマルチバイト文字が U+FFFD (「�」) に化ける。改行 (0x0A) は
|
|
157
|
+
* UTF-8 のマルチバイト列の途中には現れないので、改行位置で Buffer を切れば文字は割れない。
|
|
158
|
+
*/
|
|
146
159
|
_onStdout(chunk) {
|
|
147
|
-
|
|
160
|
+
let buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), "utf8")
|
|
161
|
+
if (this._pendingNeedsRescan) {
|
|
162
|
+
// 前回 listener の例外で中断した。持ち越し分に完結した行が残っているので先頭から探し直す。
|
|
163
|
+
buf = Buffer.concat([...this._pendingChunks, buf])
|
|
164
|
+
this._pendingChunks = []
|
|
165
|
+
this._pendingNeedsRescan = false
|
|
166
|
+
}
|
|
167
|
+
let start = 0
|
|
148
168
|
let idx
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
continue
|
|
169
|
+
let completed = false
|
|
170
|
+
// listener が例外を投げても、この断片の未処理分を失わない (旧実装と同じく次回へ持ち越す)。
|
|
171
|
+
try {
|
|
172
|
+
while ((idx = buf.indexOf(0x0a, start)) >= 0) {
|
|
173
|
+
const tail = buf.subarray(start, idx)
|
|
174
|
+
const line = (
|
|
175
|
+
this._pendingChunks.length
|
|
176
|
+
? Buffer.concat([...this._pendingChunks, tail])
|
|
177
|
+
: tail
|
|
178
|
+
).toString("utf8")
|
|
179
|
+
this._pendingChunks = []
|
|
180
|
+
start = idx + 1
|
|
181
|
+
if (!line.trim()) continue
|
|
182
|
+
let msg
|
|
183
|
+
try {
|
|
184
|
+
msg = JSON.parse(line)
|
|
185
|
+
} catch (err) {
|
|
186
|
+
this.logger?.warn?.(
|
|
187
|
+
{ err: err.message, line: line.slice(0, 500) },
|
|
188
|
+
"codex app-server: unparsable line (skipped)",
|
|
189
|
+
)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
this._dispatch(msg)
|
|
193
|
+
}
|
|
194
|
+
completed = true
|
|
195
|
+
} finally {
|
|
196
|
+
if (start < buf.length) {
|
|
197
|
+
this._pendingChunks.push(buf.subarray(start))
|
|
198
|
+
// 正常終了なら残りに改行は無い。例外で抜けたときだけ残りに完結行があり得る。
|
|
199
|
+
if (!completed) this._pendingNeedsRescan = true
|
|
162
200
|
}
|
|
163
|
-
this._dispatch(msg)
|
|
164
201
|
}
|
|
165
202
|
}
|
|
166
203
|
|
package/src/main.mjs
CHANGED
|
@@ -822,9 +822,10 @@ export function wireBridgeEvents(client, { claudeBridge, codexBridge }) {
|
|
|
822
822
|
resolveChatContextPct(session, event)
|
|
823
823
|
sendBridgeMessage(client, { type: "claude.event", stream_id, session_id, event })
|
|
824
824
|
})
|
|
825
|
-
claudeBridge.on("permission", ({ stream_id, session_id, cwd, request_id, tool_name, input }) => {
|
|
825
|
+
claudeBridge.on("permission", ({ stream_id, session_id, cwd, request_id, tool_name, input, agent_id }) => {
|
|
826
826
|
// 多端末共有: session_id を載せて backend が session group へ broadcast できるように
|
|
827
827
|
// する (全端末に許可ダイアログ → 先着 1 件採用)。
|
|
828
|
+
// agent_id はサブエージェントからの要求のときだけ載る (承認カードで依頼元を示す)。
|
|
828
829
|
sendBridgeMessage(client, {
|
|
829
830
|
type: "claude.permission.request",
|
|
830
831
|
stream_id,
|
|
@@ -832,6 +833,7 @@ export function wireBridgeEvents(client, { claudeBridge, codexBridge }) {
|
|
|
832
833
|
request_id,
|
|
833
834
|
tool_name,
|
|
834
835
|
input,
|
|
836
|
+
...(agent_id ? { agent_id } : {}),
|
|
835
837
|
})
|
|
836
838
|
})
|
|
837
839
|
claudeBridge.on(
|