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.
- package/README.md +23 -0
- package/apps/supervisor-api/dist/index.js +329 -24
- package/apps/supervisor-web/dist/assets/{index-CIcJHgFF.js → index-Dpf8YNxk.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-IJffwc2e.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-BnLdOiXc.js +3679 -0
- package/apps/supervisor-web/dist/index.html +3 -3
- package/bin/remote-codex.mjs +216 -12
- package/package.json +1 -1
- package/packages/claude/src/historyItems.ts +71 -0
- package/packages/claude/src/runtimeAdapter.test.ts +259 -0
- package/packages/claude/src/runtimeAdapter.ts +52 -3
- package/apps/supervisor-web/dist/assets/index-BVYGmxwf.css +0 -1
- package/apps/supervisor-web/dist/assets/thread-ui-CaDgVQIY.js +0 -3679
|
@@ -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:
|
|
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:
|
|
2344
|
+
status: outcome.status,
|
|
2345
|
+
error: outcome.error,
|
|
2297
2346
|
items: current.items,
|
|
2298
2347
|
}));
|
|
2299
2348
|
}
|