remote-codex 0.11.26 → 0.11.28

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.
@@ -884,6 +884,131 @@ describe('ClaudeRuntimeAdapter', () => {
884
884
  ).toBeLessThan(events.length - 1);
885
885
  });
886
886
 
887
+ it('maps Claude task-notification text to the active subagent tool result', async () => {
888
+ const adapter = makeAdapter((prompt) => {
889
+ if (prompt === hiddenInitPrompt()) {
890
+ return [systemInit(), result()];
891
+ }
892
+ return [
893
+ systemInit(),
894
+ {
895
+ type: 'assistant',
896
+ message: {
897
+ id: 'msg_agent',
898
+ type: 'message',
899
+ role: 'assistant',
900
+ model: 'sonnet',
901
+ content: [
902
+ {
903
+ type: 'tool_use',
904
+ id: 'toolu_agent_xml',
905
+ name: 'Task',
906
+ input: {
907
+ description: 'Audit docs',
908
+ prompt: 'Audit docs.',
909
+ },
910
+ },
911
+ ],
912
+ usage: {} as any,
913
+ },
914
+ parent_tool_use_id: null,
915
+ uuid: '00000000-0000-4000-8000-000000000041' as any,
916
+ session_id: 'claude-session-1',
917
+ },
918
+ {
919
+ type: 'user',
920
+ message: {
921
+ role: 'user',
922
+ content:
923
+ '<task-notification>\\n<task-id>task-1</task-id>\\n<tool-use-id>toolu_agent_xml</tool-use-id>\\n<status>completed</status>\\n<summary>Agent \"Audit docs\" finished</summary>\\n<result>Found one stale runbook.</result>\\n</task-notification>',
924
+ },
925
+ parent_tool_use_id: null,
926
+ uuid: '00000000-0000-4000-8000-000000000042' as any,
927
+ session_id: 'claude-session-1',
928
+ },
929
+ result(),
930
+ ];
931
+ });
932
+ const events: AgentRuntimeEvent[] = [];
933
+ adapter.on('event', (event) => events.push(event));
934
+
935
+ await adapter.startTurn({
936
+ providerSessionId: 'claude-session-1',
937
+ displayTurnId: 'turn-subagent-xml',
938
+ prompt: 'Use a subagent',
939
+ } as any);
940
+ await wait();
941
+
942
+ const completed = events.at(-1);
943
+ expect(completed).toMatchObject({
944
+ type: 'turn.completed',
945
+ turn: {
946
+ providerTurnId: 'turn-subagent-xml',
947
+ status: 'completed',
948
+ },
949
+ });
950
+ expect(completed?.type === 'turn.completed' ? completed.turn.items : []).toEqual([
951
+ expect.objectContaining({ id: 'turn-subagent-xml:user', kind: 'userMessage' }),
952
+ expect.objectContaining({
953
+ id: 'toolu_agent_xml',
954
+ kind: 'agentToolCall',
955
+ status: 'completed',
956
+ detailText: expect.stringContaining('Found one stale runbook.'),
957
+ }),
958
+ ]);
959
+ });
960
+
961
+ it('marks Claude session limit assistant messages as failed even when the SDK result succeeds', async () => {
962
+ const adapter = makeAdapter((prompt) => {
963
+ if (prompt === hiddenInitPrompt()) {
964
+ return [systemInit(), result()];
965
+ }
966
+ return [
967
+ systemInit(),
968
+ {
969
+ type: 'assistant',
970
+ message: {
971
+ id: 'msg_limit',
972
+ type: 'message',
973
+ role: 'assistant',
974
+ model: 'sonnet',
975
+ content: [
976
+ {
977
+ type: 'text',
978
+ text: "You've hit your session limit · resets 10pm (America/Toronto)",
979
+ },
980
+ ],
981
+ usage: {} as any,
982
+ },
983
+ parent_tool_use_id: null,
984
+ uuid: '00000000-0000-4000-8000-000000000043' as any,
985
+ session_id: 'claude-session-1',
986
+ },
987
+ result(),
988
+ ];
989
+ });
990
+ const events: AgentRuntimeEvent[] = [];
991
+ adapter.on('event', (event) => events.push(event));
992
+
993
+ await adapter.startTurn({
994
+ providerSessionId: 'claude-session-1',
995
+ displayTurnId: 'turn-limit',
996
+ prompt: 'Continue',
997
+ } as any);
998
+ await wait();
999
+
1000
+ expect(events.at(-1)).toMatchObject({
1001
+ type: 'turn.completed',
1002
+ turn: {
1003
+ providerTurnId: 'turn-limit',
1004
+ status: 'failed',
1005
+ error: {
1006
+ message: expect.stringContaining("You've hit your session limit"),
1007
+ },
1008
+ },
1009
+ });
1010
+ });
1011
+
887
1012
  it('filters Claude generated titles derived from the synthetic init prompt', async () => {
888
1013
  const adapter = new ClaudeRuntimeAdapter({
889
1014
  home: '/tmp/claude-home',
@@ -1858,6 +1983,140 @@ describe('ClaudeRuntimeAdapter', () => {
1858
1983
  ]);
1859
1984
  });
1860
1985
 
1986
+ it('keeps historical task notifications inside the originating subagent tool call', async () => {
1987
+ const adapter = new ClaudeRuntimeAdapter({
1988
+ home: '/tmp/claude-home',
1989
+ query: (() => new FakeQuery([])) as any,
1990
+ getSessionInfo: (async () => ({
1991
+ sessionId: 'claude-session-1',
1992
+ summary: 'Existing session',
1993
+ lastModified: 1_772_000_000_000,
1994
+ createdAt: 1_771_000_000_000,
1995
+ cwd: '/tmp/workspace',
1996
+ })) as any,
1997
+ listSessions: (async () => []) as any,
1998
+ getSessionMessages: (async () => [
1999
+ {
2000
+ type: 'user',
2001
+ uuid: '019e4657-bd3c-72d1-b59d-324ed8a4b1ec',
2002
+ session_id: 'claude-session-1',
2003
+ message: { role: 'user', content: 'Audit both docs.' },
2004
+ parent_tool_use_id: null,
2005
+ },
2006
+ {
2007
+ type: 'assistant',
2008
+ uuid: 'assistant-task',
2009
+ session_id: 'claude-session-1',
2010
+ message: {
2011
+ role: 'assistant',
2012
+ content: [
2013
+ {
2014
+ type: 'tool_use',
2015
+ id: 'toolu_task_a',
2016
+ name: 'Task',
2017
+ input: {
2018
+ description: 'Audit infra docs',
2019
+ prompt: 'Audit infra docs.',
2020
+ },
2021
+ },
2022
+ ],
2023
+ },
2024
+ parent_tool_use_id: null,
2025
+ },
2026
+ {
2027
+ type: 'user',
2028
+ uuid: 'task-notification-a',
2029
+ session_id: 'claude-session-1',
2030
+ message: {
2031
+ role: 'user',
2032
+ content:
2033
+ '<task-notification>\\n<task-id>task-a</task-id>\\n<tool-use-id>toolu_task_a</tool-use-id>\\n<status>completed</status>\\n<summary>Agent \"Audit infra docs\" finished</summary>\\n<result>Infra docs need one migration checklist.</result>\\n</task-notification>',
2034
+ },
2035
+ parent_tool_use_id: null,
2036
+ },
2037
+ {
2038
+ type: 'assistant',
2039
+ uuid: 'assistant-final',
2040
+ session_id: 'claude-session-1',
2041
+ message: {
2042
+ role: 'assistant',
2043
+ content: [{ type: 'text', text: 'Infra audit complete.' }],
2044
+ },
2045
+ parent_tool_use_id: null,
2046
+ },
2047
+ ] satisfies SessionMessage[]) as any,
2048
+ });
2049
+
2050
+ const session = await adapter.readSession('claude-session-1');
2051
+ expect(session.turns).toHaveLength(1);
2052
+ expect(session.turns[0]?.items).toEqual([
2053
+ expect.objectContaining({ kind: 'userMessage', text: 'Audit both docs.' }),
2054
+ expect.objectContaining({
2055
+ id: 'toolu_task_a',
2056
+ kind: 'agentToolCall',
2057
+ status: 'completed',
2058
+ detailText: expect.stringContaining('Infra docs need one migration checklist.'),
2059
+ }),
2060
+ expect.objectContaining({ kind: 'agentMessage', text: 'Infra audit complete.' }),
2061
+ ]);
2062
+ expect(session.turns[0]?.items).not.toEqual(
2063
+ expect.arrayContaining([
2064
+ expect.objectContaining({
2065
+ kind: 'userMessage',
2066
+ text: expect.stringContaining('<task-notification>'),
2067
+ }),
2068
+ ]),
2069
+ );
2070
+ });
2071
+
2072
+ it('restores historical Claude session limit turns as failed', async () => {
2073
+ const adapter = new ClaudeRuntimeAdapter({
2074
+ home: '/tmp/claude-home',
2075
+ query: (() => new FakeQuery([])) as any,
2076
+ getSessionInfo: (async () => ({
2077
+ sessionId: 'claude-session-1',
2078
+ summary: 'Existing session',
2079
+ lastModified: 1_772_000_000_000,
2080
+ createdAt: 1_771_000_000_000,
2081
+ cwd: '/tmp/workspace',
2082
+ })) as any,
2083
+ listSessions: (async () => []) as any,
2084
+ getSessionMessages: (async () => [
2085
+ {
2086
+ type: 'user',
2087
+ uuid: '019e4657-bd3c-72d1-b59d-324ed8a4b1ec',
2088
+ session_id: 'claude-session-1',
2089
+ message: { role: 'user', content: 'Continue anyway.' },
2090
+ parent_tool_use_id: null,
2091
+ },
2092
+ {
2093
+ type: 'assistant',
2094
+ uuid: 'assistant-limit',
2095
+ session_id: 'claude-session-1',
2096
+ message: {
2097
+ role: 'assistant',
2098
+ content: [
2099
+ {
2100
+ type: 'text',
2101
+ text: "You've hit your session limit · resets 10pm (America/Toronto)",
2102
+ },
2103
+ ],
2104
+ },
2105
+ parent_tool_use_id: null,
2106
+ },
2107
+ ] satisfies SessionMessage[]) as any,
2108
+ });
2109
+
2110
+ const session = await adapter.readSession('claude-session-1');
2111
+ expect(session.turns).toHaveLength(1);
2112
+ expect(session.turns[0]).toMatchObject({
2113
+ status: 'failed',
2114
+ error: {
2115
+ message: expect.stringContaining("You've hit your session limit"),
2116
+ },
2117
+ });
2118
+ });
2119
+
1861
2120
  it('omits Claude AskUserQuestion tool results from historical turns', async () => {
1862
2121
  const adapter = new ClaudeRuntimeAdapter({
1863
2122
  home: '/tmp/claude-home',
@@ -45,10 +45,12 @@ import {
45
45
  hiddenInitPrompt,
46
46
  isHiddenContinuationMessage,
47
47
  isHiddenInitMessage,
48
+ limitErrorFromHistoryItems,
48
49
  partialReasoningDelta,
49
50
  partialTextDelta,
50
51
  resultForToolUse,
51
52
  suppressedClaudeToolUseIds,
53
+ taskNotificationToolResult,
52
54
  toolUseFromPartialStart,
53
55
  toolUseToHistoryItem,
54
56
  toolResultBlocks,
@@ -748,6 +750,14 @@ function queryResultError(message: SDKMessage): string | null {
748
750
  return message.errors?.join('\n') || message.stop_reason || 'Claude turn failed.';
749
751
  }
750
752
 
753
+ function statusForHistoricalItems(items: AgentHistoryItem[]) {
754
+ const limitError = limitErrorFromHistoryItems(items);
755
+ return {
756
+ status: limitError ? ('failed' as const) : ('completed' as const),
757
+ error: limitError,
758
+ };
759
+ }
760
+
751
761
  function assistantMessagePayload(message: SDKMessage) {
752
762
  return message.type === 'assistant' ? message.message : null;
753
763
  }
@@ -1698,8 +1708,11 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1698
1708
  this.deleteActiveTurn(state);
1699
1709
  this.emitUsage(state);
1700
1710
  const completedAt = new Date().toISOString();
1711
+ const limitError = limitErrorFromHistoryItems(orderedItems(state));
1701
1712
  const status = state.interrupted
1702
1713
  ? 'interrupted'
1714
+ : limitError
1715
+ ? 'failed'
1703
1716
  : terminalStatus ?? 'completed';
1704
1717
  this.emitRuntimeEvent({
1705
1718
  type: 'turn.completed',
@@ -1709,7 +1722,7 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1709
1722
  providerTurnId: state.providerTurnId,
1710
1723
  startedAt: state.startedAt,
1711
1724
  status,
1712
- error: terminalError,
1725
+ error: limitError ?? terminalError,
1713
1726
  items: finalizeTurnItems(state, status, completedAt),
1714
1727
  rawTurn: rawMessages,
1715
1728
  }),
@@ -1817,6 +1830,19 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
1817
1830
  }
1818
1831
 
1819
1832
  if (message.type === 'user') {
1833
+ const taskNotification = taskNotificationToolResult(message.message);
1834
+ if (taskNotification && !state.suppressedToolUseIds.has(taskNotification.toolUseId)) {
1835
+ const item = resultForToolUse({
1836
+ toolUseId: taskNotification.toolUseId,
1837
+ result: message.tool_use_result ?? taskNotification.result,
1838
+ previous: state.items.get(taskNotification.toolUseId) ?? null,
1839
+ });
1840
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
1841
+ addOrUpdateItem(state, nextItem);
1842
+ this.emitItem(state, nextItem, 'item.completed');
1843
+ return;
1844
+ }
1845
+
1820
1846
  const rawToolResults = toolResultBlocks(message.message);
1821
1847
  const toolResults = rawToolResults.filter(
1822
1848
  (toolResult) => !state.suppressedToolUseIds.has(toolResult.toolUseId),
@@ -2190,6 +2216,25 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2190
2216
 
2191
2217
  for (const message of messages) {
2192
2218
  if (message.type === 'user') {
2219
+ const taskNotification = taskNotificationToolResult(message.message);
2220
+ if (taskNotification) {
2221
+ if (suppressedToolUseIds.has(taskNotification.toolUseId)) {
2222
+ continue;
2223
+ }
2224
+ const previous = current?.itemsById.get(taskNotification.toolUseId) ?? null;
2225
+ upsertCurrentItem(
2226
+ withHistoryItemCreatedAt(
2227
+ resultForToolUse({
2228
+ toolUseId: taskNotification.toolUseId,
2229
+ result: message.tool_use_result ?? taskNotification.result,
2230
+ previous,
2231
+ }),
2232
+ sessionMessageTimestamp(message) ?? current?.startedAt,
2233
+ ),
2234
+ );
2235
+ continue;
2236
+ }
2237
+
2193
2238
  const rawToolResults = toolResultBlocks(message.message);
2194
2239
  const toolResults = rawToolResults.filter(
2195
2240
  (toolResult) => !suppressedToolUseIds.has(toolResult.toolUseId),
@@ -2226,10 +2271,12 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2226
2271
  }
2227
2272
  skippingHiddenInit = false;
2228
2273
  if (current && current.items.length > 0) {
2274
+ const outcome = statusForHistoricalItems(current.items);
2229
2275
  turns.push(buildAgentTurn({
2230
2276
  providerTurnId: current.providerTurnId,
2231
2277
  startedAt: current.startedAt,
2232
- status: 'completed',
2278
+ status: outcome.status,
2279
+ error: outcome.error,
2233
2280
  items: current.items,
2234
2281
  }));
2235
2282
  }
@@ -2290,10 +2337,12 @@ export class ClaudeRuntimeAdapter extends EventEmitter implements AgentRuntime {
2290
2337
  }
2291
2338
 
2292
2339
  if (current && current.items.length > 0) {
2340
+ const outcome = statusForHistoricalItems(current.items);
2293
2341
  turns.push(buildAgentTurn({
2294
2342
  providerTurnId: current.providerTurnId,
2295
2343
  startedAt: current.startedAt,
2296
- status: 'completed',
2344
+ status: outcome.status,
2345
+ error: outcome.error,
2297
2346
  items: current.items,
2298
2347
  }));
2299
2348
  }