@cocorograph/hub-agent 0.8.36 → 0.8.37

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.8.36",
3
+ "version": "0.8.37",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの Claude / Codex チャットと tmux セッションを中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -394,8 +394,8 @@ function explicitSubagentRoleFromThread(thread, expectedParentThreadId) {
394
394
  const threadSpawn = thread?.source?.subAgent?.thread_spawn
395
395
  if (
396
396
  threadSpawn &&
397
- (typeof threadSpawn.parent_thread_id !== "string" ||
398
- threadSpawn.parent_thread_id !== expectedParentThreadId)
397
+ Object.prototype.hasOwnProperty.call(threadSpawn, "parent_thread_id") &&
398
+ threadSpawn.parent_thread_id !== expectedParentThreadId
399
399
  ) return null
400
400
  const directRole = typeof thread?.agentRole === "string" ? thread.agentRole.trim() : ""
401
401
  if (directRole) return directRole
@@ -767,6 +767,12 @@ export class CodexStreamBridge extends EventEmitter {
767
767
  subagents: new Map(),
768
768
  /** dynamicToolCall(spawn_agent) の item.id -> 表示メタデータ。 */
769
769
  subagentSpawnMetadata: new Map(),
770
+ /** live subAgentActivity の spawn metadata miss を child thread/read で補完する
771
+ * single-flight 台帳。key = agentThreadId, value = 進行中 Promise。 */
772
+ subagentRoleReads: new Map(),
773
+ /** role 補完を試行済みの child thread。取得不能・親不一致・RPC失敗後の後続 activity で
774
+ * 無制限に read/warn を再発火しない negative cache(session と同じ寿命)。 */
775
+ subagentRoleReadAttempted: new Set(),
770
776
  /** 実行中の背景コマンド(commandExecution)の台帳。claude 側 `_liveTasks` と同型。
771
777
  * key = `item.id`(`item/started` / `item/completed` の両方に載る唯一の安定 ID)、
772
778
  * value = { threadId, startedAt, lastSeenAt }。
@@ -1670,6 +1676,80 @@ export class CodexStreamBridge extends EventEmitter {
1670
1676
  return record
1671
1677
  }
1672
1678
 
1679
+ _recoverLiveSubagentRole(session, record, itemId) {
1680
+ if (!session || !record || record.agentExplicit === true || session.dead) return
1681
+ const agentThreadId = record.agentThreadId
1682
+ if (
1683
+ session.subagentRoleReads.has(agentThreadId) ||
1684
+ session.subagentRoleReadAttempted.has(agentThreadId)
1685
+ ) return
1686
+ session.subagentRoleReadAttempted.add(agentThreadId)
1687
+ this.logger?.warn?.(
1688
+ {
1689
+ item_id: itemId,
1690
+ parent_thread_id: session.threadId,
1691
+ subagent_thread_id: agentThreadId,
1692
+ },
1693
+ "codex live subagent spawn metadata miss; recovering role from child thread",
1694
+ )
1695
+ const attempt = (async () => {
1696
+ let result
1697
+ try {
1698
+ result = await session.client.request("thread/read", {
1699
+ threadId: agentThreadId,
1700
+ includeTurns: false,
1701
+ })
1702
+ } catch {
1703
+ this.logger?.warn?.(
1704
+ {
1705
+ item_id: itemId,
1706
+ parent_thread_id: session.threadId,
1707
+ subagent_thread_id: agentThreadId,
1708
+ },
1709
+ "codex live subagent role recovery thread/read failed",
1710
+ )
1711
+ return
1712
+ }
1713
+ if (session.dead) return
1714
+ const current = session.subagents.get(agentThreadId)
1715
+ if (!current || current.agentExplicit === true) return
1716
+ const explicitRole = explicitSubagentRoleFromThread(result?.thread, session.threadId)
1717
+ if (!explicitRole) return
1718
+ const updated = this._setSubagentPhase(
1719
+ session,
1720
+ agentThreadId,
1721
+ current.phase,
1722
+ {
1723
+ agentPath: current.agentPath,
1724
+ metadata: { agent: explicitRole, agentExplicit: true },
1725
+ atMs: current.atMs,
1726
+ },
1727
+ { force: true },
1728
+ )
1729
+ // 成功後は record.agentExplicit が再試行を止めるため negative cache は不要。
1730
+ if (updated?.agentExplicit === true) {
1731
+ session.subagentRoleReadAttempted.delete(agentThreadId)
1732
+ }
1733
+ })()
1734
+ const pending = attempt
1735
+ .catch(() => {
1736
+ this.logger?.warn?.(
1737
+ {
1738
+ item_id: itemId,
1739
+ parent_thread_id: session.threadId,
1740
+ subagent_thread_id: agentThreadId,
1741
+ },
1742
+ "codex live subagent role recovery failed",
1743
+ )
1744
+ })
1745
+ .finally(() => {
1746
+ if (session.subagentRoleReads.get(agentThreadId) === pending) {
1747
+ session.subagentRoleReads.delete(agentThreadId)
1748
+ }
1749
+ })
1750
+ session.subagentRoleReads.set(agentThreadId, pending)
1751
+ }
1752
+
1673
1753
  _observeSubagentItem(
1674
1754
  session,
1675
1755
  item,
@@ -1716,7 +1796,7 @@ export class CodexStreamBridge extends EventEmitter {
1716
1796
  }
1717
1797
  if (!phase) return
1718
1798
  const metadata = session.subagentSpawnMetadata.get(item.id) || null
1719
- this._setSubagentPhase(
1799
+ const record = this._setSubagentPhase(
1720
1800
  session,
1721
1801
  agentThreadId,
1722
1802
  phase,
@@ -1728,6 +1808,11 @@ export class CodexStreamBridge extends EventEmitter {
1728
1808
  },
1729
1809
  { emit },
1730
1810
  )
1811
+ // metadata が存在して agentExplicit=false なら agent_type 未指定という明示入力。
1812
+ // thread/read 補完は metadata lookup 自体が外れた場合だけに限定し、その指定を上書きしない。
1813
+ if (emit && record && !metadata) {
1814
+ this._recoverLiveSubagentRole(session, record, item.id)
1815
+ }
1731
1816
  }
1732
1817
 
1733
1818
  _observeSubagentTurn(session, msg) {