@cocorograph/hub-agent 0.8.33 → 0.8.35
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 +146 -45
- package/src/main.mjs +40 -24
package/package.json
CHANGED
|
@@ -621,6 +621,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
621
621
|
// 由来で表示がリセットされ得るため、live セッションが保持する高忠実度履歴を返す。
|
|
622
622
|
history,
|
|
623
623
|
historyTruncated: existing.historyTruncated === true,
|
|
624
|
+
subagents: this._subagentSnapshots(existing, history),
|
|
624
625
|
}
|
|
625
626
|
}
|
|
626
627
|
if (r.status === "superseded") {
|
|
@@ -747,8 +748,8 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
747
748
|
* deliveryId: string, sourceStreamId: string, deliveryPending: boolean,
|
|
748
749
|
* deliveryPromise: Promise<object>, settleDelivery: Function}>} */
|
|
749
750
|
pendingInjectSources: [],
|
|
750
|
-
/** 組み込み collaboration subagent
|
|
751
|
-
*
|
|
751
|
+
/** 組み込み collaboration subagent の表示用台帳。検索キーは agentThreadId、
|
|
752
|
+
* 外部へ出す安定した委譲 ID は spawn dynamicToolCall の item.id を保持する。 */
|
|
752
753
|
subagents: new Map(),
|
|
753
754
|
/** dynamicToolCall(spawn_agent) の item.id -> 表示メタデータ。 */
|
|
754
755
|
subagentSpawnMetadata: new Map(),
|
|
@@ -850,8 +851,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
850
851
|
session.historyItems = history.slice()
|
|
851
852
|
session.historyBytes = history.reduce((n, it) => n + estimateItemBytes(it), 0)
|
|
852
853
|
session.historyTruncated = historyTruncated
|
|
853
|
-
if (resuming) await this._restoreSubagentsFromHistory(session,
|
|
854
|
-
if (resuming) this._scheduleSubagentReplay(session)
|
|
854
|
+
if (resuming) await this._restoreSubagentsFromHistory(session, history)
|
|
855
855
|
// 案C: 権威的なターン状態を resume 応答から取り出してフロントへ渡す。
|
|
856
856
|
// これによりフロントは会話末尾からの推測をやめ、Thread.status で turnActive を
|
|
857
857
|
// 決められる (生成中スピナー固着バグの根治)。新規スレッドは常に idle 相当。
|
|
@@ -908,6 +908,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
908
908
|
threadId: session.threadId,
|
|
909
909
|
history,
|
|
910
910
|
historyTruncated,
|
|
911
|
+
subagents: this._subagentSnapshots(session, history),
|
|
911
912
|
threadStatus: turnState.threadStatus,
|
|
912
913
|
lastTurnStatus: turnState.lastTurnStatus,
|
|
913
914
|
turnActive: turnState.turnActive,
|
|
@@ -1078,7 +1079,6 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1078
1079
|
// 出したカードは remount/端末切替で失われ、応答が来ないとターンが永久 pending になる。新 stream へ
|
|
1079
1080
|
// 再 emit してカードを復元させる (frontend は request_id で重複排除するので二重化しない)。
|
|
1080
1081
|
this._replayPendingApprovals(session)
|
|
1081
|
-
this._scheduleSubagentReplay(session)
|
|
1082
1082
|
// 履歴は返さない: reconnect では frontend が表示を保持している (resetDisplay=false) ため
|
|
1083
1083
|
// 再 hydrate 不要。turnActive は session が保持する activeTurnId から権威的に決める。
|
|
1084
1084
|
return {
|
|
@@ -1460,6 +1460,12 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1460
1460
|
const list = session?.pendingInjectSources
|
|
1461
1461
|
if (!list || !list.length) return null
|
|
1462
1462
|
if (msg?.method !== "item/completed") return null
|
|
1463
|
+
const sourceThreadId = msg.params?.threadId
|
|
1464
|
+
if (
|
|
1465
|
+
typeof sourceThreadId === "string" &&
|
|
1466
|
+
sourceThreadId &&
|
|
1467
|
+
sourceThreadId !== session?.threadId
|
|
1468
|
+
) return null
|
|
1463
1469
|
const item = msg.params?.item
|
|
1464
1470
|
if (!item || item.type !== "userMessage") return null
|
|
1465
1471
|
const content = item.content
|
|
@@ -1582,13 +1588,14 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1582
1588
|
notification: {
|
|
1583
1589
|
method: "cockpit/subagent_state",
|
|
1584
1590
|
params: {
|
|
1585
|
-
delegationId: record.
|
|
1591
|
+
delegationId: record.delegationId,
|
|
1586
1592
|
agentThreadId: record.agentThreadId,
|
|
1587
1593
|
agentPath: record.agentPath,
|
|
1588
1594
|
phase: record.phase,
|
|
1589
1595
|
agent: record.agent,
|
|
1590
1596
|
description: record.description,
|
|
1591
1597
|
prompt: record.prompt,
|
|
1598
|
+
background: record.background === true,
|
|
1592
1599
|
atMs: record.atMs,
|
|
1593
1600
|
},
|
|
1594
1601
|
},
|
|
@@ -1609,6 +1616,14 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1609
1616
|
? details.agentPath
|
|
1610
1617
|
: previous?.agentPath || ""
|
|
1611
1618
|
const metadata = details.metadata || null
|
|
1619
|
+
const delegationId =
|
|
1620
|
+
previous?.delegationId ||
|
|
1621
|
+
(typeof details.delegationId === "string" && details.delegationId
|
|
1622
|
+
? details.delegationId
|
|
1623
|
+
: null)
|
|
1624
|
+
// raw child thread ID を委譲 ID に代用しない。spawn lifecycle 由来の安定 ID が無い
|
|
1625
|
+
// 通知は台帳へ登録せず、後続 item も unknown として未タグのまま扱う。
|
|
1626
|
+
if (typeof delegationId !== "string" || !delegationId) return null
|
|
1612
1627
|
const transitionAtMs =
|
|
1613
1628
|
previous?.phase === phase
|
|
1614
1629
|
? previous.atMs
|
|
@@ -1616,6 +1631,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1616
1631
|
? details.atMs
|
|
1617
1632
|
: Date.now()
|
|
1618
1633
|
const record = {
|
|
1634
|
+
delegationId,
|
|
1619
1635
|
agentThreadId,
|
|
1620
1636
|
agentPath,
|
|
1621
1637
|
phase,
|
|
@@ -1623,11 +1639,14 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1623
1639
|
description:
|
|
1624
1640
|
metadata?.description || previous?.description || subagentFallbackName(agentPath),
|
|
1625
1641
|
prompt: metadata?.prompt || previous?.prompt || "",
|
|
1642
|
+
// background は spawn 時に確定する属性。後続通知に metadata が無くても true を失わない。
|
|
1643
|
+
background: metadata?.background === true || previous?.background === true,
|
|
1626
1644
|
atMs: transitionAtMs,
|
|
1627
1645
|
startedAtMs:
|
|
1628
1646
|
phase === "started" && terminalSubagentPhase(previous?.phase)
|
|
1629
1647
|
? transitionAtMs
|
|
1630
1648
|
: previous?.startedAtMs ?? transitionAtMs,
|
|
1649
|
+
terminalAtMs: terminalSubagentPhase(phase) ? transitionAtMs : null,
|
|
1631
1650
|
lastEmittedPhase: previous?.lastEmittedPhase || null,
|
|
1632
1651
|
}
|
|
1633
1652
|
session.subagents.set(agentThreadId, record)
|
|
@@ -1635,8 +1654,20 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1635
1654
|
return record
|
|
1636
1655
|
}
|
|
1637
1656
|
|
|
1638
|
-
_observeSubagentItem(
|
|
1657
|
+
_observeSubagentItem(
|
|
1658
|
+
session,
|
|
1659
|
+
item,
|
|
1660
|
+
atMs = Date.now(),
|
|
1661
|
+
{ emit = true, sourceThreadId = session?.threadId } = {},
|
|
1662
|
+
) {
|
|
1639
1663
|
if (!item || typeof item !== "object") return
|
|
1664
|
+
// root thread の collaboration item だけが root subagent 台帳の正本。子 thread 内の
|
|
1665
|
+
// spawn/subAgentActivity(孫)を root の lifecycle/snapshot へ昇格しない。
|
|
1666
|
+
if (
|
|
1667
|
+
typeof sourceThreadId === "string" &&
|
|
1668
|
+
sourceThreadId &&
|
|
1669
|
+
sourceThreadId !== session?.threadId
|
|
1670
|
+
) return
|
|
1640
1671
|
if (item.type === "dynamicToolCall") {
|
|
1641
1672
|
const namespace = item.namespace
|
|
1642
1673
|
if (namespace !== "collaboration" || item.tool !== "spawn_agent" || !item.id) return
|
|
@@ -1646,6 +1677,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1646
1677
|
agent: typeof args.agent_type === "string" && args.agent_type ? args.agent_type : null,
|
|
1647
1678
|
description: typeof args.task_name === "string" && args.task_name ? args.task_name : null,
|
|
1648
1679
|
prompt: typeof args.message === "string" ? args.message : null,
|
|
1680
|
+
background: args.background === true,
|
|
1649
1681
|
}
|
|
1650
1682
|
session.subagentSpawnMetadata.set(item.id, metadata)
|
|
1651
1683
|
while (session.subagentSpawnMetadata.size > 256) {
|
|
@@ -1656,6 +1688,7 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1656
1688
|
if (item.type !== "subAgentActivity") return
|
|
1657
1689
|
const agentThreadId = item.agentThreadId
|
|
1658
1690
|
if (typeof agentThreadId !== "string" || !agentThreadId) return
|
|
1691
|
+
if (agentThreadId === session.threadId || item.agentPath === "/root") return
|
|
1659
1692
|
const previous = session.subagents.get(agentThreadId)
|
|
1660
1693
|
let phase = null
|
|
1661
1694
|
if (item.kind === "interrupted") phase = "stopped"
|
|
@@ -1668,7 +1701,12 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1668
1701
|
session,
|
|
1669
1702
|
agentThreadId,
|
|
1670
1703
|
phase,
|
|
1671
|
-
{
|
|
1704
|
+
{
|
|
1705
|
+
delegationId: item.id,
|
|
1706
|
+
agentPath: item.agentPath,
|
|
1707
|
+
metadata,
|
|
1708
|
+
atMs,
|
|
1709
|
+
},
|
|
1672
1710
|
{ emit },
|
|
1673
1711
|
)
|
|
1674
1712
|
}
|
|
@@ -1690,49 +1728,65 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1690
1728
|
? Math.round(turn.startedAt * 1000)
|
|
1691
1729
|
: Date.now()
|
|
1692
1730
|
if (msg.method === "turn/started") {
|
|
1693
|
-
this._setSubagentPhase(session, threadId, "started", {
|
|
1731
|
+
this._setSubagentPhase(session, threadId, "started", {
|
|
1732
|
+
agentPath: record.agentPath,
|
|
1733
|
+
atMs,
|
|
1734
|
+
})
|
|
1694
1735
|
} else if (msg.method === "turn/completed") {
|
|
1695
1736
|
const phase = terminalSubagentPhase(msg.params?.turn?.status)
|
|
1696
1737
|
if (phase) {
|
|
1697
|
-
this._setSubagentPhase(session, threadId, phase, {
|
|
1738
|
+
this._setSubagentPhase(session, threadId, phase, {
|
|
1739
|
+
agentPath: record.agentPath,
|
|
1740
|
+
atMs,
|
|
1741
|
+
})
|
|
1698
1742
|
}
|
|
1699
1743
|
}
|
|
1700
1744
|
return true
|
|
1701
1745
|
}
|
|
1702
1746
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1747
|
+
_subagentSnapshots(session, history = session?.historyItems) {
|
|
1748
|
+
const items = Array.isArray(history) ? history : []
|
|
1749
|
+
const startedIndexByThread = new Map()
|
|
1750
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
1751
|
+
const item = items[index]
|
|
1752
|
+
if (
|
|
1753
|
+
item?.type === "subAgentActivity" &&
|
|
1754
|
+
item.kind === "started" &&
|
|
1755
|
+
typeof item.agentThreadId === "string" &&
|
|
1756
|
+
item.agentPath !== "/root" &&
|
|
1757
|
+
item.agentThreadId !== session?.threadId &&
|
|
1758
|
+
!startedIndexByThread.has(item.agentThreadId)
|
|
1759
|
+
) {
|
|
1760
|
+
startedIndexByThread.set(item.agentThreadId, index)
|
|
1711
1761
|
}
|
|
1712
|
-
this._emitSubagentState(session, record, { force: true })
|
|
1713
1762
|
}
|
|
1763
|
+
const snapshots = []
|
|
1764
|
+
for (const record of session?.subagents?.values?.() || []) {
|
|
1765
|
+
const startedHistoryIndex = startedIndexByThread.get(record.agentThreadId)
|
|
1766
|
+
if (startedHistoryIndex === undefined) continue
|
|
1767
|
+
snapshots.push({
|
|
1768
|
+
delegationId: record.delegationId,
|
|
1769
|
+
agentThreadId: record.agentThreadId,
|
|
1770
|
+
agentPath: record.agentPath,
|
|
1771
|
+
agent: record.agent,
|
|
1772
|
+
description: record.description,
|
|
1773
|
+
prompt: record.prompt,
|
|
1774
|
+
background: record.background === true,
|
|
1775
|
+
startedHistoryIndex,
|
|
1776
|
+
phase: record.phase,
|
|
1777
|
+
startedAtMs: record.startedAtMs,
|
|
1778
|
+
terminalAtMs: record.terminalAtMs,
|
|
1779
|
+
})
|
|
1780
|
+
}
|
|
1781
|
+
return snapshots
|
|
1714
1782
|
}
|
|
1715
1783
|
|
|
1716
|
-
|
|
1717
|
-
//
|
|
1718
|
-
//
|
|
1719
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
async _restoreSubagentsFromHistory(session, thread) {
|
|
1725
|
-
// 表示用 history は巨大スレッド対策で末尾だけに truncate されるため、復元元には使わない。
|
|
1726
|
-
// root の全 turn を逐次走査し、subAgentActivity の安定 ID を取りこぼさない。
|
|
1727
|
-
for (const turn of Array.isArray(thread?.turns) ? thread.turns : []) {
|
|
1728
|
-
const atMs =
|
|
1729
|
-
typeof turn?.completedAt === "number"
|
|
1730
|
-
? Math.round(turn.completedAt * 1000)
|
|
1731
|
-
: typeof turn?.startedAt === "number"
|
|
1732
|
-
? Math.round(turn.startedAt * 1000)
|
|
1733
|
-
: Date.now()
|
|
1734
|
-
for (const item of Array.isArray(turn?.items) ? turn.items : []) {
|
|
1735
|
-
this._observeSubagentItem(session, item, atMs, { emit: false })
|
|
1784
|
+
async _restoreSubagentsFromHistory(session, history) {
|
|
1785
|
+
// ready に載せる表示用 history と同じ範囲だけを復元元にする。truncate 外の開始
|
|
1786
|
+
// anchor を拾うと、対応する履歴が無い lifecycle card が末尾へ浮くため全履歴は走査しない。
|
|
1787
|
+
for (const item of Array.isArray(history) ? history : []) {
|
|
1788
|
+
if (item?.type === "dynamicToolCall" || item?.kind === "started") {
|
|
1789
|
+
this._observeSubagentItem(session, item, item?.at_ms, { emit: false })
|
|
1736
1790
|
}
|
|
1737
1791
|
}
|
|
1738
1792
|
const records = [...session.subagents.values()]
|
|
@@ -1745,6 +1799,8 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1745
1799
|
threadId: record.agentThreadId,
|
|
1746
1800
|
includeTurns: true,
|
|
1747
1801
|
})
|
|
1802
|
+
// RPC 待ち中に live lifecycle が進んだ場合、stale read で上書き・削除しない。
|
|
1803
|
+
if (session.subagents.get(record.agentThreadId) !== record) continue
|
|
1748
1804
|
const thread = result?.thread
|
|
1749
1805
|
const turns = Array.isArray(thread?.turns) ? thread.turns : []
|
|
1750
1806
|
const lastTurn = turns.at(-1)
|
|
@@ -1763,23 +1819,34 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1763
1819
|
{ agentPath: record.agentPath, atMs: lastTurnAtMs },
|
|
1764
1820
|
{ emit: false },
|
|
1765
1821
|
)
|
|
1766
|
-
} else if (
|
|
1822
|
+
} else if (
|
|
1823
|
+
thread?.status?.type === "active" &&
|
|
1824
|
+
(!lastTurn || lastTurn.status === "inProgress")
|
|
1825
|
+
) {
|
|
1767
1826
|
this._setSubagentPhase(
|
|
1768
1827
|
session,
|
|
1769
1828
|
record.agentThreadId,
|
|
1770
1829
|
"started",
|
|
1771
|
-
{
|
|
1830
|
+
{
|
|
1831
|
+
agentPath: record.agentPath,
|
|
1832
|
+
atMs: record.startedAtMs,
|
|
1833
|
+
},
|
|
1772
1834
|
{ emit: false },
|
|
1773
1835
|
)
|
|
1836
|
+
} else {
|
|
1837
|
+
session.subagents.delete(record.agentThreadId)
|
|
1774
1838
|
}
|
|
1775
1839
|
} catch (err) {
|
|
1840
|
+
if (session.subagents.get(record.agentThreadId) === record) {
|
|
1841
|
+
session.subagents.delete(record.agentThreadId)
|
|
1842
|
+
}
|
|
1776
1843
|
this.logger?.warn?.(
|
|
1777
1844
|
{
|
|
1778
1845
|
err: err?.message || String(err),
|
|
1779
1846
|
thread_id: session.threadId,
|
|
1780
1847
|
subagent_thread_id: record.agentThreadId,
|
|
1781
1848
|
},
|
|
1782
|
-
"codex subagent thread/read failed during resume",
|
|
1849
|
+
"codex subagent thread/read failed during resume; excluded from ready snapshot",
|
|
1783
1850
|
)
|
|
1784
1851
|
}
|
|
1785
1852
|
}
|
|
@@ -1841,13 +1908,47 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1841
1908
|
// (タグ済み or 素) を history と emit の両方で使う。一致しなければ null → 素の params のまま。
|
|
1842
1909
|
// ※ hub-agent 再起動 (cold resume) では historyItems が揮発するため青バブルへ戻る (claude の
|
|
1843
1910
|
// jsonl at-rest と同じ許容範囲。source 永続マップは TTL/ID 再利用の複雑さに見合わないため不採用)。
|
|
1911
|
+
const eventThreadId = msg?.params?.threadId
|
|
1844
1912
|
const taggedParams = this._tagInjectSourceOnItem(session, msg)
|
|
1845
|
-
|
|
1913
|
+
let effectiveParams = taggedParams || msg.params
|
|
1914
|
+
const childRecord =
|
|
1915
|
+
typeof eventThreadId === "string" ? session.subagents.get(eventThreadId) : null
|
|
1916
|
+
const childTextDelta =
|
|
1917
|
+
msg.method === "item/agentMessage/delta" ||
|
|
1918
|
+
msg.method === "item/reasoning/summaryTextDelta" ||
|
|
1919
|
+
msg.method === "item/reasoning/textDelta"
|
|
1920
|
+
if (childRecord && childTextDelta) {
|
|
1921
|
+
// 子の message/reasoning 途中 delta を root liveText へ混ぜない。前景・背景とも
|
|
1922
|
+
// attribution 付き item/completed が表示の正本で、root/unknown delta は従来どおり
|
|
1923
|
+
// fail-open で通す。
|
|
1924
|
+
return
|
|
1925
|
+
}
|
|
1926
|
+
if (
|
|
1927
|
+
childRecord &&
|
|
1928
|
+
(msg.method === "item/started" || msg.method === "item/completed") &&
|
|
1929
|
+
effectiveParams?.item &&
|
|
1930
|
+
typeof effectiveParams.item === "object"
|
|
1931
|
+
) {
|
|
1932
|
+
// app-server 所有の item は変更せず、既知の子 thread だけを attribution 付きで複製する。
|
|
1933
|
+
// unknown/root を推測で子扱いしない。delegationId は child thread ID ではなく、spawn
|
|
1934
|
+
// lifecycle が保持する安定 ID を使う。
|
|
1935
|
+
const attributedItem = {
|
|
1936
|
+
...effectiveParams.item,
|
|
1937
|
+
_hubSubagent: {
|
|
1938
|
+
delegationId: childRecord.delegationId,
|
|
1939
|
+
threadId: eventThreadId,
|
|
1940
|
+
runtime: "codex",
|
|
1941
|
+
background: childRecord.background === true,
|
|
1942
|
+
},
|
|
1943
|
+
}
|
|
1944
|
+
effectiveParams = { ...effectiveParams, item: attributedItem }
|
|
1945
|
+
}
|
|
1846
1946
|
if (msg.method === "item/started" || msg.method === "item/completed") {
|
|
1847
1947
|
this._observeSubagentItem(
|
|
1848
1948
|
session,
|
|
1849
1949
|
effectiveParams?.item,
|
|
1850
1950
|
effectiveParams?.completedAtMs || Date.now(),
|
|
1951
|
+
{ sourceThreadId: eventThreadId },
|
|
1851
1952
|
)
|
|
1852
1953
|
}
|
|
1853
1954
|
const childTurnNotification =
|
|
@@ -1917,8 +2018,8 @@ export class CodexStreamBridge extends EventEmitter {
|
|
|
1917
2018
|
void this._pumpInputs(session)
|
|
1918
2019
|
break
|
|
1919
2020
|
case "item/completed":
|
|
1920
|
-
//
|
|
1921
|
-
//
|
|
2021
|
+
// 完了した ThreadItem を高忠実度履歴に追記する(cwd reattach で返す用)。
|
|
2022
|
+
// attribution/source 済み item (effectiveParams.item) を積み、emit と同じ clone を使う。
|
|
1922
2023
|
this._appendHistoryItem(session, effectiveParams?.item, effectiveParams?.completedAtMs)
|
|
1923
2024
|
break
|
|
1924
2025
|
default:
|
package/src/main.mjs
CHANGED
|
@@ -2052,6 +2052,43 @@ export function buildCodexChatPatch(status, contextPct, threadId, isAuthority) {
|
|
|
2052
2052
|
return patch
|
|
2053
2053
|
}
|
|
2054
2054
|
|
|
2055
|
+
/** codex.ready の wire payload を組み立てる。history は同じ配列参照を保持し、
|
|
2056
|
+
* started_history_index が指す位置を main 側の加工でずらさない。表示を保持する reconnect
|
|
2057
|
+
* (`info.subagents` 未定義) では subagents キー自体を省略し、空 snapshot で既存表示を消さない。 */
|
|
2058
|
+
export function buildCodexReadyPayload(streamId, info, queueState) {
|
|
2059
|
+
const history = Array.isArray(info?.history) ? info.history : []
|
|
2060
|
+
const payload = {
|
|
2061
|
+
type: "codex.ready",
|
|
2062
|
+
stream_id: streamId,
|
|
2063
|
+
resuming: info?.resuming === true,
|
|
2064
|
+
thread_id: info?.threadId || null,
|
|
2065
|
+
history,
|
|
2066
|
+
history_truncated: info?.historyTruncated === true,
|
|
2067
|
+
turn_active: info?.turnActive === true,
|
|
2068
|
+
thread_status: info?.threadStatus || null,
|
|
2069
|
+
last_turn_status: info?.lastTurnStatus || null,
|
|
2070
|
+
queue_state: queueState,
|
|
2071
|
+
}
|
|
2072
|
+
if (Array.isArray(info?.subagents)) {
|
|
2073
|
+
payload.subagents = info.subagents.map((subagent) => ({
|
|
2074
|
+
// 現契約では delegation_id と agent_thread_id は同じ child thread ID の alias。
|
|
2075
|
+
delegation_id: subagent.delegationId,
|
|
2076
|
+
agent_thread_id: subagent.agentThreadId,
|
|
2077
|
+
agent_path: subagent.agentPath,
|
|
2078
|
+
agent: subagent.agent,
|
|
2079
|
+
// description は spawn_agent.task_name の既存 alias(人物の表示名ではない)。
|
|
2080
|
+
description: subagent.description,
|
|
2081
|
+
prompt: subagent.prompt,
|
|
2082
|
+
background: subagent.background === true,
|
|
2083
|
+
started_history_index: subagent.startedHistoryIndex,
|
|
2084
|
+
phase: subagent.phase,
|
|
2085
|
+
started_at_ms: subagent.startedAtMs,
|
|
2086
|
+
terminal_at_ms: subagent.terminalAtMs,
|
|
2087
|
+
}))
|
|
2088
|
+
}
|
|
2089
|
+
return payload
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2055
2092
|
/**
|
|
2056
2093
|
* T06407 増分2 (codex-review round2 major#2): usage.response の codexSessions を、集計に
|
|
2057
2094
|
* 使った producer が **今なお cwd の権威**である entry だけへ絞る post-await ガード。
|
|
@@ -4131,30 +4168,9 @@ async function dispatch(msg, ctx) {
|
|
|
4131
4168
|
// すると bridge が rebind し、fresh の二重生成を防ぐ (browser の stream_id は都度変わる)。
|
|
4132
4169
|
attachId: msg.attach_id || undefined,
|
|
4133
4170
|
})
|
|
4134
|
-
ctx.client.send(
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
resuming: info.resuming,
|
|
4138
|
-
thread_id: info.threadId,
|
|
4139
|
-
// Slice 6: 履歴 hydrate。resume 時のみ非空 (thread/start は常に [])。
|
|
4140
|
-
// consumers.py の per-stream relay は content-blind なので backend 側の
|
|
4141
|
-
// 変更は不要 (_AGENT_PER_STREAM_TYPES の codex.ready がそのまま運ぶ)。
|
|
4142
|
-
history: info.history || [],
|
|
4143
|
-
// T06499: 巨大スレッドで末尾 N ターンに絞った場合 true。browser は
|
|
4144
|
-
// 「以前の履歴は省略されています」バナーを出す (会話は resume 済みで継続可)。
|
|
4145
|
-
history_truncated: info.historyTruncated === true,
|
|
4146
|
-
// 案C (権威的ターン状態): フロントは会話末尾からの推測 (deriveTurnActive
|
|
4147
|
-
// FromHistory) をやめ、Codex が明示する Thread.status で turnActive を決める。
|
|
4148
|
-
// resume 後は新プロセスで実行中ターンが無く thread_status='idle' が返るため、
|
|
4149
|
-
// 生成中スピナー固着 (中断/ツールのみ終了ターンで末尾が user メッセージになる
|
|
4150
|
-
// ケース) が根治する。
|
|
4151
|
-
turn_active: info.turnActive === true,
|
|
4152
|
-
thread_status: info.threadStatus || null,
|
|
4153
|
-
last_turn_status: info.lastTurnStatus || null,
|
|
4154
|
-
// Codex の通常送信 FIFO。ready と同じ封筒に初期 snapshot を載せ、
|
|
4155
|
-
// reattach / reload でも送信待ち表示を即時復元する。
|
|
4156
|
-
queue_state: ctx.codexBridge.queueState(stream_id),
|
|
4157
|
-
})
|
|
4171
|
+
ctx.client.send(
|
|
4172
|
+
buildCodexReadyPayload(stream_id, info, ctx.codexBridge.queueState(stream_id)),
|
|
4173
|
+
)
|
|
4158
4174
|
// S1 (2026-07): claude.attach と対称の identity 正本化。Codex は thread/start
|
|
4159
4175
|
// 応答時点で threadId が確定するため (Claude の fresh とは異なり)、fresh でも
|
|
4160
4176
|
// 既に分かっている threadId を sessionId として記録する (直後の turn/started が
|