@magclaw/cli-core 0.1.34 → 0.1.36
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/cli.js +512 -33
- package/src/mcp-bridge.js +72 -3
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -61,6 +61,79 @@ function now() {
|
|
|
61
61
|
return new Date().toISOString();
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function claudeStreamEvents(raw) {
|
|
65
|
+
if (!raw || typeof raw !== 'object') return [];
|
|
66
|
+
const event = raw;
|
|
67
|
+
const output = [];
|
|
68
|
+
if (event.type === 'system' && event.subtype === 'init') {
|
|
69
|
+
output.push({
|
|
70
|
+
type: 'system',
|
|
71
|
+
sessionId: event.session_id || event.sessionId || '',
|
|
72
|
+
model: event.model || '',
|
|
73
|
+
cwd: event.cwd || '',
|
|
74
|
+
});
|
|
75
|
+
return output;
|
|
76
|
+
}
|
|
77
|
+
const content = Array.isArray(event.message?.content) ? event.message.content : [];
|
|
78
|
+
if (event.type === 'assistant') {
|
|
79
|
+
for (const block of content) {
|
|
80
|
+
if (block?.type === 'text' && typeof block.text === 'string' && block.text) {
|
|
81
|
+
output.push({ type: 'text', delta: block.text });
|
|
82
|
+
} else if (block?.type === 'thinking' && typeof block.thinking === 'string' && block.thinking) {
|
|
83
|
+
output.push({ type: 'thinking', delta: block.thinking });
|
|
84
|
+
} else if (block?.type === 'tool_use' && block.id && block.name) {
|
|
85
|
+
output.push({ type: 'tool_use', id: block.id, name: block.name, input: block.input });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
if (event.type === 'user') {
|
|
91
|
+
for (const block of content) {
|
|
92
|
+
if (block?.type === 'tool_result' && block.tool_use_id) {
|
|
93
|
+
output.push({
|
|
94
|
+
type: 'tool_result',
|
|
95
|
+
id: block.tool_use_id,
|
|
96
|
+
output: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
|
|
97
|
+
isError: block.is_error === true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return output;
|
|
102
|
+
}
|
|
103
|
+
if (event.type === 'result') {
|
|
104
|
+
if (event.usage) {
|
|
105
|
+
output.push({
|
|
106
|
+
type: 'usage',
|
|
107
|
+
inputTokens: event.usage.input_tokens,
|
|
108
|
+
outputTokens: event.usage.output_tokens,
|
|
109
|
+
costUsd: event.total_cost_usd,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
output.push({ type: 'done', sessionId: event.session_id || event.sessionId || '' });
|
|
113
|
+
}
|
|
114
|
+
return output;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function claudeToolActivityDetail(event) {
|
|
118
|
+
if (!event || typeof event !== 'object') return 'Claude Code activity';
|
|
119
|
+
if (event.type === 'tool_use') return `Claude Code using ${event.name || 'tool'}`;
|
|
120
|
+
if (event.type === 'tool_result') return event.isError ? 'Claude Code tool returned an error' : 'Claude Code tool completed';
|
|
121
|
+
if (event.type === 'thinking') return 'Claude Code thinking';
|
|
122
|
+
if (event.type === 'usage') return `Claude Code usage input=${event.inputTokens || 0} output=${event.outputTokens || 0}`;
|
|
123
|
+
return 'Claude Code activity';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function codexStderrRuntimeError(text = '') {
|
|
127
|
+
const detail = String(text || '').trim();
|
|
128
|
+
if (!detail) return '';
|
|
129
|
+
const lower = detail.toLowerCase();
|
|
130
|
+
if (lower.includes('responses_websocket') && lower.includes('error')) return detail.slice(0, 2000);
|
|
131
|
+
if (lower.includes('failed to connect to websocket') && lower.includes('/v1/responses')) return detail.slice(0, 2000);
|
|
132
|
+
if (lower.includes('authentication') && lower.includes('openai')) return detail.slice(0, 2000);
|
|
133
|
+
if (lower.includes('not logged in') || lower.includes('login is required')) return detail.slice(0, 2000);
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
|
|
64
137
|
function packageInfoFromSpec(packageSpec = '') {
|
|
65
138
|
const match = String(packageSpec || '').trim().match(/^(@magclaw\/(?:daemon|computer))(?:@(.+))?$/);
|
|
66
139
|
return {
|
|
@@ -1721,6 +1794,60 @@ function contextSnippet(value, limit = 240) {
|
|
|
1721
1794
|
return text.length >= limit ? `${text.slice(0, Math.max(0, limit - 3)).trim()}...` : text;
|
|
1722
1795
|
}
|
|
1723
1796
|
|
|
1797
|
+
function contextImageMimeFromName(value = '') {
|
|
1798
|
+
const name = String(value || '').toLowerCase().split(/[?#]/)[0];
|
|
1799
|
+
if (name.endsWith('.png')) return 'image/png';
|
|
1800
|
+
if (name.endsWith('.jpg') || name.endsWith('.jpeg')) return 'image/jpeg';
|
|
1801
|
+
if (name.endsWith('.webp')) return 'image/webp';
|
|
1802
|
+
if (name.endsWith('.gif')) return 'image/gif';
|
|
1803
|
+
if (name.endsWith('.svg')) return 'image/svg+xml';
|
|
1804
|
+
return '';
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
function contextDataImageUrl(value = '') {
|
|
1808
|
+
const text = String(value || '').trim();
|
|
1809
|
+
return /^data:image\//i.test(text) ? text : '';
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function contextImageType(reference = {}) {
|
|
1813
|
+
const explicit = String(reference.type || reference.mime || reference.mimeType || '').toLowerCase();
|
|
1814
|
+
if (explicit.startsWith('image/')) return explicit;
|
|
1815
|
+
const data = contextDataImageUrl(reference.dataUrl || reference.url || reference.downloadUrl);
|
|
1816
|
+
if (data) return data.match(/^data:([^;,]+)[;,]/i)?.[1]?.toLowerCase() || 'image';
|
|
1817
|
+
return contextImageMimeFromName(reference.name || reference.filename || reference.url || reference.downloadUrl || reference.path || reference.description);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function isContextImageReference(reference = {}) {
|
|
1821
|
+
return contextImageType(reference).startsWith('image/');
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
function remoteImageUrl(value = '', serverUrl = '', fallbackPath = '') {
|
|
1825
|
+
const raw = String(value || '').trim();
|
|
1826
|
+
if (contextDataImageUrl(raw)) return raw;
|
|
1827
|
+
const base = String(serverUrl || DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
1828
|
+
const candidate = raw || String(fallbackPath || '').trim();
|
|
1829
|
+
if (!candidate) return '';
|
|
1830
|
+
if (candidate.startsWith('/')) {
|
|
1831
|
+
try {
|
|
1832
|
+
return new URL(candidate, base).toString();
|
|
1833
|
+
} catch {
|
|
1834
|
+
return '';
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
if (/^https?:\/\//i.test(candidate)) {
|
|
1838
|
+
try {
|
|
1839
|
+
const parsed = new URL(candidate);
|
|
1840
|
+
if (['0.0.0.0', '127.0.0.1', 'localhost', '::1'].includes(parsed.hostname) && base) {
|
|
1841
|
+
return new URL(`${parsed.pathname}${parsed.search}${parsed.hash}`, base).toString();
|
|
1842
|
+
}
|
|
1843
|
+
return parsed.toString();
|
|
1844
|
+
} catch {
|
|
1845
|
+
return '';
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return '';
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1724
1851
|
function contextActorName(pack, id) {
|
|
1725
1852
|
const value = String(id || '').trim();
|
|
1726
1853
|
if (!value) return 'unknown';
|
|
@@ -1767,6 +1894,50 @@ function renderContextTasks(pack) {
|
|
|
1767
1894
|
}).join('\n');
|
|
1768
1895
|
}
|
|
1769
1896
|
|
|
1897
|
+
function renderContextAttachments(pack) {
|
|
1898
|
+
const attachments = contextArray(pack?.attachments);
|
|
1899
|
+
if (!attachments.length) return '- (none)';
|
|
1900
|
+
return attachments.map((item) => {
|
|
1901
|
+
const details = [
|
|
1902
|
+
item.id ? `id=${item.id}` : '',
|
|
1903
|
+
item.messageId ? `from msg=${item.messageId}` : '',
|
|
1904
|
+
item.source ? `source=${item.source}` : '',
|
|
1905
|
+
item.path ? `path=${item.path}` : '',
|
|
1906
|
+
item.url ? `url=${item.url}` : '',
|
|
1907
|
+
item.id ? `tool=read_attachment(attachmentId="${item.id}")` : '',
|
|
1908
|
+
].filter(Boolean).join(', ');
|
|
1909
|
+
return `- ${item.name || item.filename || item.id || 'attachment'} ${item.type || item.mime || 'file'} ${Number(item.bytes || item.sizeBytes || 0)} bytes${details ? ` (${details})` : ''}`;
|
|
1910
|
+
}).join('\n');
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function renderContextTargetAgentAvatar(pack) {
|
|
1914
|
+
const avatar = pack?.targetAgent?.avatar;
|
|
1915
|
+
if (!avatar || avatar.kind === 'none') return '';
|
|
1916
|
+
const description = avatar.description ? ` (${avatar.description})` : '';
|
|
1917
|
+
if (avatar.visualInput !== false && isContextImageReference(avatar)) {
|
|
1918
|
+
return `- Your profile avatar: image supplied as visual input${description}. Use it when the user asks what your avatar shows.`;
|
|
1919
|
+
}
|
|
1920
|
+
return `- Your profile avatar: ${avatar.description || 'configured'}, but no visual input is available.`;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
function contextParticipantAvatarVisualInputs(pack, targetAgentId = '') {
|
|
1924
|
+
return compactContextParticipants(pack, targetAgentId).selected.filter((item) => (
|
|
1925
|
+
item.id !== targetAgentId
|
|
1926
|
+
&& item.type === 'agent'
|
|
1927
|
+
&& item.avatar
|
|
1928
|
+
&& item.avatar.kind !== 'none'
|
|
1929
|
+
&& item.avatar.visualInput !== false
|
|
1930
|
+
&& isContextImageReference(item.avatar)
|
|
1931
|
+
));
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function renderContextParticipantAvatarInputs(pack, targetAgentId = '') {
|
|
1935
|
+
const visible = contextParticipantAvatarVisualInputs(pack, targetAgentId);
|
|
1936
|
+
if (!visible.length) return '';
|
|
1937
|
+
const names = visible.map((item) => `@${item.name || item.id}`).join(', ');
|
|
1938
|
+
return `- Participant avatar visual inputs: ${names}. Use these when comparing an uploaded image to another Agent avatar; call read_agent_avatar if the relevant Agent is omitted.`;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1770
1941
|
function renderContextEventMembers(pack, event = {}) {
|
|
1771
1942
|
const ids = [
|
|
1772
1943
|
...contextArray(event.memberIds),
|
|
@@ -1889,6 +2060,8 @@ function renderRemoteAgentContextPack(pack, targetAgentId = '') {
|
|
|
1889
2060
|
`- Space: ${pack.space?.type || 'space'} (${pack.space?.visibility || 'public'}${pack.space?.defaultChannel ? ', default workspace channel' : ''})`,
|
|
1890
2061
|
pack.space?.description ? `- Channel description: ${contextSnippet(pack.space.description, 180)}` : '',
|
|
1891
2062
|
`- Participants shown: ${participants.selected.map((item) => renderContextParticipant(item, targetAgentId)).join(', ') || '(none)'}`,
|
|
2063
|
+
renderContextTargetAgentAvatar(pack),
|
|
2064
|
+
renderContextParticipantAvatarInputs(pack, targetAgentId),
|
|
1892
2065
|
participants.omitted ? `- Participants omitted: ${participants.omitted}. Use list_agents/read_agent_profile or search_agent_memory when a broader roster or specialties matter.` : '',
|
|
1893
2066
|
pack.space?.type === 'channel' && !pack.space?.defaultChannel ? renderContextSuggestedMembers(pack) : '',
|
|
1894
2067
|
'',
|
|
@@ -1917,9 +2090,12 @@ function renderRemoteAgentContextPack(pack, targetAgentId = '') {
|
|
|
1917
2090
|
'Relevant tasks:',
|
|
1918
2091
|
renderContextTasks(pack),
|
|
1919
2092
|
'',
|
|
2093
|
+
'Visible attachment metadata and original-file tools:',
|
|
2094
|
+
renderContextAttachments(pack),
|
|
2095
|
+
'',
|
|
1920
2096
|
renderContextPeerMemory(pack),
|
|
1921
2097
|
'',
|
|
1922
|
-
'Progressive context tools: list_agents, read_agent_profile, read_history, search_messages, search_agent_memory, read_agent_memory, read_agent_file, and list_tasks are available through MagClaw MCP.',
|
|
2098
|
+
'Progressive context tools: list_agents, read_agent_profile, read_agent_avatar, read_history, search_messages, list_attachments, read_attachment, search_agent_memory, read_agent_memory, read_agent_file, and list_tasks are available through MagClaw MCP.',
|
|
1923
2099
|
'For "who can we bring in" or agent suitability questions, use the server member list above first; call list_agents without a target for the server-wide agent roster, because target filters to the current channel.',
|
|
1924
2100
|
'For agent capability or specialty questions, use peer memory first; if memory is empty or weak, search_messages/read_history for earlier user role assignments before saying the fact is unknown.',
|
|
1925
2101
|
'Use this compact snapshot first. Call the tools only when the answer depends on omitted participants, deeper history, memory, or task details.',
|
|
@@ -2020,11 +2196,14 @@ function canonicalMagClawToolName(name) {
|
|
|
2020
2196
|
'send_message',
|
|
2021
2197
|
'read_history',
|
|
2022
2198
|
'search_messages',
|
|
2199
|
+
'list_attachments',
|
|
2200
|
+
'read_attachment',
|
|
2023
2201
|
'search_agent_memory',
|
|
2024
2202
|
'read_agent_memory',
|
|
2025
2203
|
'read_agent_file',
|
|
2026
2204
|
'list_agents',
|
|
2027
2205
|
'read_agent_profile',
|
|
2206
|
+
'read_agent_avatar',
|
|
2028
2207
|
'write_memory',
|
|
2029
2208
|
'list_tasks',
|
|
2030
2209
|
'create_tasks',
|
|
@@ -2508,11 +2687,14 @@ function daemonSkillTools() {
|
|
|
2508
2687
|
'send_message',
|
|
2509
2688
|
'read_history',
|
|
2510
2689
|
'search_messages',
|
|
2690
|
+
'list_attachments',
|
|
2691
|
+
'read_attachment',
|
|
2511
2692
|
'search_agent_memory',
|
|
2512
2693
|
'read_agent_memory',
|
|
2513
2694
|
'read_agent_file',
|
|
2514
2695
|
'list_agents',
|
|
2515
2696
|
'read_agent_profile',
|
|
2697
|
+
'read_agent_avatar',
|
|
2516
2698
|
'write_memory',
|
|
2517
2699
|
'list_tasks',
|
|
2518
2700
|
'create_tasks',
|
|
@@ -2808,9 +2990,12 @@ class CodexAgentSession {
|
|
|
2808
2990
|
this.completedToolCallIds = new Set();
|
|
2809
2991
|
this.activeTurnToolSignatures = new Set();
|
|
2810
2992
|
this.activeTurnUsedSendMessage = false;
|
|
2993
|
+
this.activeTurnSawResponseDelta = false;
|
|
2994
|
+
this.activeTurnDeltaItemIds = new Set();
|
|
2811
2995
|
this.codexMessageQueue = Promise.resolve();
|
|
2812
2996
|
this.streamActivityTimer = null;
|
|
2813
2997
|
this.pendingStreamActivity = null;
|
|
2998
|
+
this.lastRuntimeError = '';
|
|
2814
2999
|
this.trajectoryCoalesceMs = envInteger(this.env, 'MAGCLAW_DAEMON_TRAJECTORY_COALESCE_MS', DEFAULT_TRAJECTORY_COALESCE_MS, { min: 0, max: 5_000 });
|
|
2815
3000
|
}
|
|
2816
3001
|
|
|
@@ -2960,6 +3145,38 @@ class CodexAgentSession {
|
|
|
2960
3145
|
if (payload) this.send(payload);
|
|
2961
3146
|
}
|
|
2962
3147
|
|
|
3148
|
+
async reportRuntimeError(errorText, rawText = '') {
|
|
3149
|
+
const error = String(errorText || rawText || 'Codex runtime error.').trim().slice(0, 2000);
|
|
3150
|
+
if (!error) return;
|
|
3151
|
+
if (this.status === 'error' && this.lastRuntimeError === error) return;
|
|
3152
|
+
this.lastRuntimeError = error;
|
|
3153
|
+
const activity = {
|
|
3154
|
+
source: 'codex-stderr',
|
|
3155
|
+
error,
|
|
3156
|
+
text: String(rawText || error).trim().slice(0, 2000),
|
|
3157
|
+
at: now(),
|
|
3158
|
+
};
|
|
3159
|
+
this.send({
|
|
3160
|
+
type: 'agent:error',
|
|
3161
|
+
commandId: this.activeDeliveryId || undefined,
|
|
3162
|
+
deliveryId: this.activeDeliveryId || null,
|
|
3163
|
+
agentId: this.agent.id,
|
|
3164
|
+
error,
|
|
3165
|
+
});
|
|
3166
|
+
if (this.activeDeliveryId) {
|
|
3167
|
+
await this.markDelivery(this.activeDeliveryId, 'failed', {
|
|
3168
|
+
agentId: this.agent.id,
|
|
3169
|
+
sessionKey: this.sessionKey || null,
|
|
3170
|
+
messageId: this.lastSourceMessage?.id || null,
|
|
3171
|
+
workItemId: this.lastSourceMessage?.workItemId || null,
|
|
3172
|
+
error,
|
|
3173
|
+
}).catch((markError) => {
|
|
3174
|
+
logWarning('daemon', `Failed to mark delivery ${this.activeDeliveryId} failed after Codex runtime error: ${markError.message}`);
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
this.sendStatus('error', activity);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
2963
3180
|
async requestMagClawTool(pathname, { method = 'GET', query = {}, body = null } = {}) {
|
|
2964
3181
|
const url = new URL(`${this.serverUrl.replace(/\/+$/, '')}${pathname}`);
|
|
2965
3182
|
for (const [key, value] of Object.entries(query || {})) {
|
|
@@ -2998,6 +3215,92 @@ class CodexAgentSession {
|
|
|
2998
3215
|
}
|
|
2999
3216
|
}
|
|
3000
3217
|
|
|
3218
|
+
async readAttachmentImageInput(reference = {}) {
|
|
3219
|
+
const attachmentId = String(reference.id || reference.attachmentId || reference.attachment_id || '').trim();
|
|
3220
|
+
if (!attachmentId) return null;
|
|
3221
|
+
try {
|
|
3222
|
+
const data = await this.requestMagClawTool('/api/agent-tools/attachments/read', {
|
|
3223
|
+
query: {
|
|
3224
|
+
agentId: this.agent.id,
|
|
3225
|
+
attachmentId,
|
|
3226
|
+
maxBytes: 8 * 1024 * 1024,
|
|
3227
|
+
},
|
|
3228
|
+
});
|
|
3229
|
+
const dataUrl = contextDataImageUrl(data?.dataUrl);
|
|
3230
|
+
if (!dataUrl || data?.file?.truncated) return null;
|
|
3231
|
+
return {
|
|
3232
|
+
key: `attachment:${attachmentId}`,
|
|
3233
|
+
input: { type: 'image', url: dataUrl },
|
|
3234
|
+
};
|
|
3235
|
+
} catch (error) {
|
|
3236
|
+
logWarning('attachments', `Could not read image attachment ${attachmentId} for agent ${this.agent.id}: ${error.message}`);
|
|
3237
|
+
return null;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
async imageInputFromContextReference(reference = {}, { preferReadAttachment = false } = {}) {
|
|
3242
|
+
if (!isContextImageReference(reference)) return null;
|
|
3243
|
+
if (preferReadAttachment) {
|
|
3244
|
+
const resolved = await this.readAttachmentImageInput(reference);
|
|
3245
|
+
if (resolved) return resolved;
|
|
3246
|
+
}
|
|
3247
|
+
const dataUrl = contextDataImageUrl(reference.dataUrl || reference.url || reference.downloadUrl);
|
|
3248
|
+
if (dataUrl) {
|
|
3249
|
+
return {
|
|
3250
|
+
key: `url:${dataUrl}`,
|
|
3251
|
+
input: { type: 'image', url: dataUrl },
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
const url = remoteImageUrl(reference.url || reference.downloadUrl || '', this.serverUrl, reference.description);
|
|
3255
|
+
if (url) {
|
|
3256
|
+
return {
|
|
3257
|
+
key: `url:${url}`,
|
|
3258
|
+
input: { type: 'image', url },
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
const filePath = String(reference.path || '').trim();
|
|
3262
|
+
if (filePath && existsSync(filePath)) {
|
|
3263
|
+
return {
|
|
3264
|
+
key: `path:${filePath}`,
|
|
3265
|
+
input: { type: 'localImage', path: filePath },
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
return null;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
async imageInputsForDelivery(message = {}) {
|
|
3272
|
+
const inputs = [];
|
|
3273
|
+
const seen = new Set();
|
|
3274
|
+
const pack = message?.contextPack || {};
|
|
3275
|
+
const attachments = contextArray(pack.attachments);
|
|
3276
|
+
for (const attachment of attachments) {
|
|
3277
|
+
const resolved = await this.imageInputFromContextReference(attachment, { preferReadAttachment: true });
|
|
3278
|
+
if (!resolved || seen.has(resolved.key)) continue;
|
|
3279
|
+
seen.add(resolved.key);
|
|
3280
|
+
inputs.push(resolved.input);
|
|
3281
|
+
}
|
|
3282
|
+
const avatar = pack?.targetAgent?.avatar || null;
|
|
3283
|
+
if (avatar?.visualInput !== false) {
|
|
3284
|
+
const resolved = await this.imageInputFromContextReference(avatar);
|
|
3285
|
+
if (resolved && !seen.has(resolved.key)) {
|
|
3286
|
+
seen.add(resolved.key);
|
|
3287
|
+
inputs.push(resolved.input);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
const targetAgentId = String(pack?.targetAgentId || pack?.targetAgent?.id || this.agent.id || '');
|
|
3291
|
+
for (const participant of contextArray(pack.participants)) {
|
|
3292
|
+
if (participant?.type !== 'agent') continue;
|
|
3293
|
+
if (targetAgentId && String(participant.id || '') === targetAgentId) continue;
|
|
3294
|
+
const participantAvatar = participant.avatar || null;
|
|
3295
|
+
if (!participantAvatar || participantAvatar.visualInput === false || !isContextImageReference(participantAvatar)) continue;
|
|
3296
|
+
const resolved = await this.imageInputFromContextReference(participantAvatar);
|
|
3297
|
+
if (!resolved || seen.has(resolved.key)) continue;
|
|
3298
|
+
seen.add(resolved.key);
|
|
3299
|
+
inputs.push(resolved.input);
|
|
3300
|
+
}
|
|
3301
|
+
return inputs;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3001
3304
|
async executeMagClawTool(name, rawArgs = {}) {
|
|
3002
3305
|
const args = { ...rawArgs, agentId: rawArgs.agentId || this.agent.id };
|
|
3003
3306
|
switch (name) {
|
|
@@ -3035,6 +3338,25 @@ class CodexAgentSession {
|
|
|
3035
3338
|
limit: args.limit,
|
|
3036
3339
|
},
|
|
3037
3340
|
});
|
|
3341
|
+
case 'list_attachments':
|
|
3342
|
+
return this.requestMagClawTool('/api/agent-tools/attachments', {
|
|
3343
|
+
query: {
|
|
3344
|
+
agentId: args.agentId,
|
|
3345
|
+
target: args.target || args.channel,
|
|
3346
|
+
workItemId: args.workItemId || args.work_item_id,
|
|
3347
|
+
messageId: args.messageId || args.message_id,
|
|
3348
|
+
limit: args.limit,
|
|
3349
|
+
},
|
|
3350
|
+
});
|
|
3351
|
+
case 'read_attachment':
|
|
3352
|
+
return this.requestMagClawTool('/api/agent-tools/attachments/read', {
|
|
3353
|
+
query: {
|
|
3354
|
+
agentId: args.agentId,
|
|
3355
|
+
attachmentId: args.attachmentId || args.attachment_id || args.id,
|
|
3356
|
+
maxBytes: args.maxBytes || args.max_bytes,
|
|
3357
|
+
format: args.format,
|
|
3358
|
+
},
|
|
3359
|
+
});
|
|
3038
3360
|
case 'search_agent_memory':
|
|
3039
3361
|
return this.requestMagClawTool('/api/agent-tools/memory/search', {
|
|
3040
3362
|
query: {
|
|
@@ -3076,6 +3398,14 @@ class CodexAgentSession {
|
|
|
3076
3398
|
targetAgentId: args.targetAgentId || args.targetAgent,
|
|
3077
3399
|
},
|
|
3078
3400
|
});
|
|
3401
|
+
case 'read_agent_avatar':
|
|
3402
|
+
return this.requestMagClawTool('/api/agent-tools/agents/avatar/read', {
|
|
3403
|
+
query: {
|
|
3404
|
+
agentId: args.agentId,
|
|
3405
|
+
targetAgentId: args.targetAgentId || args.targetAgent,
|
|
3406
|
+
maxBytes: args.maxBytes || args.max_bytes,
|
|
3407
|
+
},
|
|
3408
|
+
});
|
|
3079
3409
|
case 'write_memory':
|
|
3080
3410
|
{
|
|
3081
3411
|
const local = await writeDaemonLocalMemory(this.agentDir(), this.agent, args);
|
|
@@ -3161,6 +3491,18 @@ class CodexAgentSession {
|
|
|
3161
3491
|
}
|
|
3162
3492
|
}
|
|
3163
3493
|
|
|
3494
|
+
appendCompletedAgentText(text = '', { hadDelta = false } = {}) {
|
|
3495
|
+
const value = String(text || '');
|
|
3496
|
+
if (!value) return;
|
|
3497
|
+
if (hadDelta) {
|
|
3498
|
+
if (this.responseBuffer.endsWith(value) || this.responseBuffer.includes(value)) return;
|
|
3499
|
+
if (value.startsWith(this.responseBuffer)) this.responseBuffer = value;
|
|
3500
|
+
else this.responseBuffer += value;
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
if (!this.responseBuffer.includes(value)) this.responseBuffer += value;
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3164
3506
|
async executeCodexToolItem(item = {}, requestId = null, params = {}) {
|
|
3165
3507
|
const callId = codexToolCallId(item) || String(params.callId || params.call_id || '');
|
|
3166
3508
|
const name = canonicalMagClawToolName(codexToolName(item));
|
|
@@ -3257,10 +3599,18 @@ class CodexAgentSession {
|
|
|
3257
3599
|
this.child.stderr.on('data', (chunk) => {
|
|
3258
3600
|
const text = chunk.toString().trim();
|
|
3259
3601
|
if (!text) return;
|
|
3602
|
+
const runtimeError = codexStderrRuntimeError(text);
|
|
3603
|
+
if (runtimeError) {
|
|
3604
|
+
this.reportRuntimeError(runtimeError, text).catch((error) => {
|
|
3605
|
+
logWarning('daemon', `Failed to report Codex runtime error for ${this.agent.id}: ${error.message}`);
|
|
3606
|
+
});
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3260
3609
|
this.send({
|
|
3261
3610
|
type: 'agent:activity',
|
|
3262
3611
|
agentId: this.agent.id,
|
|
3263
3612
|
status: this.status || 'working',
|
|
3613
|
+
deliveryId: this.activeDeliveryId || null,
|
|
3264
3614
|
activity: { source: 'codex-stderr', text: text.slice(0, 2000), at: now() },
|
|
3265
3615
|
});
|
|
3266
3616
|
});
|
|
@@ -3301,19 +3651,23 @@ class CodexAgentSession {
|
|
|
3301
3651
|
this.pendingPrompts.push({ prompt, message, workItem, deliveryId });
|
|
3302
3652
|
return;
|
|
3303
3653
|
}
|
|
3304
|
-
this.startTurn(prompt, message, workItem, deliveryId);
|
|
3654
|
+
await this.startTurn(prompt, message, workItem, deliveryId);
|
|
3305
3655
|
}
|
|
3306
3656
|
|
|
3307
|
-
startTurn(prompt, message = {}, workItem = null, deliveryId = '') {
|
|
3657
|
+
async startTurn(prompt, message = {}, workItem = null, deliveryId = '') {
|
|
3308
3658
|
if (!this.threadId) return false;
|
|
3309
3659
|
this.activeDeliveryId = deliveryId || '';
|
|
3310
3660
|
this.activeTurnToolSignatures = new Set();
|
|
3311
3661
|
this.activeTurnUsedSendMessage = false;
|
|
3662
|
+
this.activeTurnSawResponseDelta = false;
|
|
3663
|
+
this.activeTurnDeltaItemIds = new Set();
|
|
3664
|
+
this.lastRuntimeError = '';
|
|
3312
3665
|
const model = this.agent.model || undefined;
|
|
3313
3666
|
const effort = this.agent.reasoningEffort || undefined;
|
|
3667
|
+
const imageInputs = await this.imageInputsForDelivery(message);
|
|
3314
3668
|
const params = {
|
|
3315
3669
|
threadId: this.threadId,
|
|
3316
|
-
input: [{ type: 'text', text: prompt }],
|
|
3670
|
+
input: [{ type: 'text', text: prompt }, ...imageInputs],
|
|
3317
3671
|
approvalPolicy: codexApprovalPolicy(this.env),
|
|
3318
3672
|
...(model ? { model } : {}),
|
|
3319
3673
|
...(effort ? { effort } : {}),
|
|
@@ -3382,7 +3736,7 @@ class CodexAgentSession {
|
|
|
3382
3736
|
this.send({ type: 'agent:session', agentId: this.agent.id, status: 'idle', sessionId: this.threadId, sessionKey: this.sessionKey || null });
|
|
3383
3737
|
this.sendStatus('idle', { source: '@magclaw/daemon', detail: 'Codex session ready', at: now() });
|
|
3384
3738
|
const queued = this.pendingPrompts.splice(0);
|
|
3385
|
-
for (const item of queued) this.startTurn(item.prompt, item.message, item.workItem, item.deliveryId);
|
|
3739
|
+
for (const item of queued) await this.startTurn(item.prompt, item.message, item.workItem, item.deliveryId);
|
|
3386
3740
|
} else if (pending?.method === 'turn/start' || pending?.method === 'turn/steer') {
|
|
3387
3741
|
this.activeTurnId = message.result?.turn?.id || message.result?.turnId || this.activeTurnId;
|
|
3388
3742
|
}
|
|
@@ -3418,6 +3772,9 @@ class CodexAgentSession {
|
|
|
3418
3772
|
}
|
|
3419
3773
|
if (method === 'item/agentMessage/delta' || method === 'response/output_text/delta') {
|
|
3420
3774
|
this.responseBuffer += String(params.delta || params.text || '');
|
|
3775
|
+
const itemId = String(params.itemId || params.item_id || params.item?.id || '');
|
|
3776
|
+
if (itemId) this.activeTurnDeltaItemIds.add(itemId);
|
|
3777
|
+
this.activeTurnSawResponseDelta = true;
|
|
3421
3778
|
this.queueCodexStreamActivity();
|
|
3422
3779
|
return;
|
|
3423
3780
|
}
|
|
@@ -3425,7 +3782,10 @@ class CodexAgentSession {
|
|
|
3425
3782
|
const item = params.item || {};
|
|
3426
3783
|
if (await this.executeCodexToolItem(item, null, params)) return;
|
|
3427
3784
|
const text = item?.text || item?.message || params.text || '';
|
|
3428
|
-
|
|
3785
|
+
const itemId = String(item?.id || item?.itemId || item?.item_id || '');
|
|
3786
|
+
this.appendCompletedAgentText(text, {
|
|
3787
|
+
hadDelta: Boolean((itemId && this.activeTurnDeltaItemIds.has(itemId)) || this.activeTurnSawResponseDelta),
|
|
3788
|
+
});
|
|
3429
3789
|
return;
|
|
3430
3790
|
}
|
|
3431
3791
|
if (method === 'turn/completed' || method === 'turn/failed') {
|
|
@@ -3457,6 +3817,8 @@ class CodexAgentSession {
|
|
|
3457
3817
|
this.responseBuffer = '';
|
|
3458
3818
|
this.activeTurnId = '';
|
|
3459
3819
|
this.activeTurnUsedSendMessage = false;
|
|
3820
|
+
this.activeTurnSawResponseDelta = false;
|
|
3821
|
+
this.activeTurnDeltaItemIds.clear();
|
|
3460
3822
|
this.sendStatus(method === 'turn/completed' ? 'idle' : 'error', {
|
|
3461
3823
|
source: '@magclaw/daemon',
|
|
3462
3824
|
detail: method === 'turn/completed' ? 'Turn completed' : 'Turn failed',
|
|
@@ -3495,6 +3857,11 @@ class ClaudeAgentSession {
|
|
|
3495
3857
|
this.status = 'offline';
|
|
3496
3858
|
this.started = false;
|
|
3497
3859
|
this.activeDeliveryId = '';
|
|
3860
|
+
this.responseBuffer = '';
|
|
3861
|
+
this.lastSourceMessage = null;
|
|
3862
|
+
this.pendingMessageDelta = null;
|
|
3863
|
+
this.messageDeltaTimer = null;
|
|
3864
|
+
this.trajectoryCoalesceMs = envInteger(this.env, 'MAGCLAW_DAEMON_TRAJECTORY_COALESCE_MS', DEFAULT_TRAJECTORY_COALESCE_MS, { min: 0, max: 5_000 });
|
|
3498
3865
|
}
|
|
3499
3866
|
|
|
3500
3867
|
agentDir() {
|
|
@@ -3558,6 +3925,87 @@ class ClaudeAgentSession {
|
|
|
3558
3925
|
});
|
|
3559
3926
|
}
|
|
3560
3927
|
|
|
3928
|
+
queueMessageDelta(delta = '') {
|
|
3929
|
+
const body = this.responseBuffer.trim();
|
|
3930
|
+
if (!body) return;
|
|
3931
|
+
this.pendingMessageDelta = {
|
|
3932
|
+
type: 'agent:message_delta',
|
|
3933
|
+
agentId: this.agent.id,
|
|
3934
|
+
deliveryId: this.activeDeliveryId || null,
|
|
3935
|
+
payload: {
|
|
3936
|
+
body,
|
|
3937
|
+
delta: String(delta || ''),
|
|
3938
|
+
message: this.lastSourceMessage || null,
|
|
3939
|
+
sourceMessage: this.lastSourceMessage || null,
|
|
3940
|
+
spaceType: this.lastSourceMessage?.spaceType || 'channel',
|
|
3941
|
+
spaceId: this.lastSourceMessage?.spaceId || 'chan_all',
|
|
3942
|
+
parentMessageId: this.lastSourceMessage?.parentMessageId || null,
|
|
3943
|
+
idempotencyKey: this.activeDeliveryId || null,
|
|
3944
|
+
},
|
|
3945
|
+
};
|
|
3946
|
+
if (this.trajectoryCoalesceMs <= 0) {
|
|
3947
|
+
this.flushMessageDelta();
|
|
3948
|
+
return;
|
|
3949
|
+
}
|
|
3950
|
+
if (this.messageDeltaTimer) return;
|
|
3951
|
+
this.messageDeltaTimer = setTimeout(() => {
|
|
3952
|
+
this.messageDeltaTimer = null;
|
|
3953
|
+
this.flushMessageDelta();
|
|
3954
|
+
}, this.trajectoryCoalesceMs);
|
|
3955
|
+
this.messageDeltaTimer.unref?.();
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
flushMessageDelta() {
|
|
3959
|
+
if (this.messageDeltaTimer) {
|
|
3960
|
+
clearTimeout(this.messageDeltaTimer);
|
|
3961
|
+
this.messageDeltaTimer = null;
|
|
3962
|
+
}
|
|
3963
|
+
const payload = this.pendingMessageDelta;
|
|
3964
|
+
this.pendingMessageDelta = null;
|
|
3965
|
+
if (payload) this.send(payload);
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
handleClaudeStreamEvent(event) {
|
|
3969
|
+
if (event.type === 'system') {
|
|
3970
|
+
if (event.sessionId) {
|
|
3971
|
+
this.send({
|
|
3972
|
+
type: 'agent:session',
|
|
3973
|
+
agentId: this.agent.id,
|
|
3974
|
+
status: this.status,
|
|
3975
|
+
sessionId: event.sessionId,
|
|
3976
|
+
sessionKey: null,
|
|
3977
|
+
});
|
|
3978
|
+
}
|
|
3979
|
+
return;
|
|
3980
|
+
}
|
|
3981
|
+
if (event.type === 'text') {
|
|
3982
|
+
this.responseBuffer += String(event.delta || '');
|
|
3983
|
+
this.queueMessageDelta(event.delta || '');
|
|
3984
|
+
this.send({
|
|
3985
|
+
type: 'agent:activity',
|
|
3986
|
+
agentId: this.agent.id,
|
|
3987
|
+
status: 'working',
|
|
3988
|
+
deliveryId: this.activeDeliveryId || null,
|
|
3989
|
+
activity: { source: 'claude-code-stream', chars: this.responseBuffer.length, at: now() },
|
|
3990
|
+
});
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
if (event.type === 'thinking' || event.type === 'tool_use' || event.type === 'tool_result' || event.type === 'usage') {
|
|
3994
|
+
this.send({
|
|
3995
|
+
type: 'agent:activity',
|
|
3996
|
+
agentId: this.agent.id,
|
|
3997
|
+
status: event.type === 'thinking' ? 'thinking' : 'working',
|
|
3998
|
+
deliveryId: this.activeDeliveryId || null,
|
|
3999
|
+
activity: {
|
|
4000
|
+
source: 'claude-code-stream',
|
|
4001
|
+
phase: event.type,
|
|
4002
|
+
detail: claudeToolActivityDetail(event),
|
|
4003
|
+
at: now(),
|
|
4004
|
+
},
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
|
|
3561
4009
|
async start() {
|
|
3562
4010
|
if (this.started) return;
|
|
3563
4011
|
await this.prepare();
|
|
@@ -3568,11 +4016,12 @@ class ClaudeAgentSession {
|
|
|
3568
4016
|
async deliver(message = {}, workItem = null, deliveryId = '') {
|
|
3569
4017
|
await this.start();
|
|
3570
4018
|
this.activeDeliveryId = deliveryId || '';
|
|
4019
|
+
this.responseBuffer = '';
|
|
4020
|
+
this.lastSourceMessage = message || null;
|
|
3571
4021
|
const prompt = deliveryPrompt(this.agent, message, workItem);
|
|
3572
4022
|
const claudeCommand = this.env.CLAUDE_PATH || 'claude';
|
|
3573
|
-
const args = ['--
|
|
4023
|
+
const args = ['-p', prompt, '--output-format', 'stream-json', '--verbose'];
|
|
3574
4024
|
if (this.agent.model) args.push('--model', String(this.agent.model));
|
|
3575
|
-
args.push(prompt);
|
|
3576
4025
|
const timeoutMs = Number(this.env.MAGCLAW_DAEMON_RUNTIME_TIMEOUT_MS || 10 * 60 * 1000);
|
|
3577
4026
|
if (this.activeDeliveryId) {
|
|
3578
4027
|
await this.markDelivery(this.activeDeliveryId, 'started', {
|
|
@@ -3583,31 +4032,33 @@ class ClaudeAgentSession {
|
|
|
3583
4032
|
}
|
|
3584
4033
|
this.sendStatus('thinking', { source: 'claude-code', detail: 'Claude Code turn started', at: now() });
|
|
3585
4034
|
await new Promise((resolve) => {
|
|
3586
|
-
let stdout = '';
|
|
3587
4035
|
let stderr = '';
|
|
4036
|
+
let stdoutBuffer = '';
|
|
3588
4037
|
let settled = false;
|
|
4038
|
+
const finalMessageFrame = (body) => ({
|
|
4039
|
+
type: 'agent:message',
|
|
4040
|
+
agentId: this.agent.id,
|
|
4041
|
+
deliveryId: this.activeDeliveryId || null,
|
|
4042
|
+
payload: {
|
|
4043
|
+
body,
|
|
4044
|
+
message,
|
|
4045
|
+
sourceMessage: message,
|
|
4046
|
+
spaceType: message.spaceType || 'channel',
|
|
4047
|
+
spaceId: message.spaceId || 'chan_all',
|
|
4048
|
+
parentMessageId: message.parentMessageId || null,
|
|
4049
|
+
idempotencyKey: this.activeDeliveryId || null,
|
|
4050
|
+
},
|
|
4051
|
+
});
|
|
3589
4052
|
const finish = (status, detail) => {
|
|
3590
4053
|
if (settled) return;
|
|
3591
4054
|
settled = true;
|
|
3592
4055
|
clearTimeout(timer);
|
|
4056
|
+
this.flushMessageDelta();
|
|
3593
4057
|
this.child = null;
|
|
4058
|
+
const body = this.responseBuffer.trim();
|
|
3594
4059
|
if (status === 'idle') {
|
|
3595
|
-
const body = stdout.trim();
|
|
3596
4060
|
if (body) {
|
|
3597
|
-
const frame =
|
|
3598
|
-
type: 'agent:message',
|
|
3599
|
-
agentId: this.agent.id,
|
|
3600
|
-
deliveryId: this.activeDeliveryId || null,
|
|
3601
|
-
payload: {
|
|
3602
|
-
body,
|
|
3603
|
-
message,
|
|
3604
|
-
sourceMessage: message,
|
|
3605
|
-
spaceType: message.spaceType || 'channel',
|
|
3606
|
-
spaceId: message.spaceId || 'chan_all',
|
|
3607
|
-
parentMessageId: message.parentMessageId || null,
|
|
3608
|
-
idempotencyKey: this.activeDeliveryId || null,
|
|
3609
|
-
},
|
|
3610
|
-
};
|
|
4061
|
+
const frame = finalMessageFrame(body);
|
|
3611
4062
|
this.send(frame);
|
|
3612
4063
|
this.markDelivery(this.activeDeliveryId, 'completed', { resultFrame: frame }).catch(() => {});
|
|
3613
4064
|
}
|
|
@@ -3615,8 +4066,10 @@ class ClaudeAgentSession {
|
|
|
3615
4066
|
this.sendStatus('idle', { source: 'claude-code', detail: detail || 'Claude Code turn completed', at: now() });
|
|
3616
4067
|
} else {
|
|
3617
4068
|
const error = detail || stderr.trim() || 'Claude Code failed.';
|
|
4069
|
+
const frame = body ? finalMessageFrame(body) : null;
|
|
4070
|
+
if (frame) this.send(frame);
|
|
3618
4071
|
this.send({ type: 'agent:error', commandId: this.activeDeliveryId || undefined, deliveryId: this.activeDeliveryId || null, agentId: this.agent.id, error });
|
|
3619
|
-
this.markDelivery(this.activeDeliveryId, 'failed', { error }).catch(() => {});
|
|
4072
|
+
this.markDelivery(this.activeDeliveryId, 'failed', { error, ...(frame ? { resultFrame: frame } : {}) }).catch(() => {});
|
|
3620
4073
|
this.sendStatus('error', { source: 'claude-code', error, at: now() });
|
|
3621
4074
|
}
|
|
3622
4075
|
this.activeDeliveryId = '';
|
|
@@ -3639,19 +4092,40 @@ class ClaudeAgentSession {
|
|
|
3639
4092
|
finish('error', 'Claude Code session timed out.');
|
|
3640
4093
|
}, timeoutMs);
|
|
3641
4094
|
this.child.stdout.on('data', (chunk) => {
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
4095
|
+
stdoutBuffer += chunk.toString();
|
|
4096
|
+
const lines = stdoutBuffer.split(/\r?\n/);
|
|
4097
|
+
stdoutBuffer = lines.pop() || '';
|
|
4098
|
+
for (const line of lines) {
|
|
4099
|
+
if (!line.trim()) continue;
|
|
4100
|
+
try {
|
|
4101
|
+
const raw = JSON.parse(line);
|
|
4102
|
+
for (const event of claudeStreamEvents(raw)) this.handleClaudeStreamEvent(event);
|
|
4103
|
+
} catch (error) {
|
|
4104
|
+
stderr += `${line}\n`;
|
|
4105
|
+
this.send({
|
|
4106
|
+
type: 'agent:activity',
|
|
4107
|
+
agentId: this.agent.id,
|
|
4108
|
+
status: 'working',
|
|
4109
|
+
deliveryId: this.activeDeliveryId || null,
|
|
4110
|
+
activity: { source: 'claude-code-stream', error: `Invalid stream JSON: ${error.message}`, at: now() },
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
3649
4114
|
});
|
|
3650
4115
|
this.child.stderr.on('data', (chunk) => {
|
|
3651
4116
|
stderr += chunk.toString();
|
|
3652
4117
|
});
|
|
3653
4118
|
this.child.on('error', (error) => finish('error', error.message));
|
|
3654
4119
|
this.child.on('close', (code, signal) => {
|
|
4120
|
+
if (stdoutBuffer.trim()) {
|
|
4121
|
+
try {
|
|
4122
|
+
const raw = JSON.parse(stdoutBuffer.trim());
|
|
4123
|
+
for (const event of claudeStreamEvents(raw)) this.handleClaudeStreamEvent(event);
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
stderr += `${stdoutBuffer}\n`;
|
|
4126
|
+
}
|
|
4127
|
+
stdoutBuffer = '';
|
|
4128
|
+
}
|
|
3655
4129
|
if (code === 0) finish('idle');
|
|
3656
4130
|
else finish('error', stderr.trim() || `Claude Code exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`);
|
|
3657
4131
|
});
|
|
@@ -3660,6 +4134,7 @@ class ClaudeAgentSession {
|
|
|
3660
4134
|
|
|
3661
4135
|
stop() {
|
|
3662
4136
|
this.status = 'stopping';
|
|
4137
|
+
this.flushMessageDelta();
|
|
3663
4138
|
if (this.child) this.child.kill('SIGTERM');
|
|
3664
4139
|
this.started = false;
|
|
3665
4140
|
}
|
|
@@ -4600,7 +5075,11 @@ class MagClawDaemon {
|
|
|
4600
5075
|
}
|
|
4601
5076
|
const sessionContext = {
|
|
4602
5077
|
sessionKey: message.payload?.sessionKey || message.sessionKey || '',
|
|
4603
|
-
workspaceId: message.
|
|
5078
|
+
workspaceId: message.payload?.message?.workspaceId
|
|
5079
|
+
|| message.payload?.workItem?.workspaceId
|
|
5080
|
+
|| message.payload?.workspaceId
|
|
5081
|
+
|| message.workspaceId
|
|
5082
|
+
|| '',
|
|
4604
5083
|
message: message.payload?.message || {},
|
|
4605
5084
|
};
|
|
4606
5085
|
const sessionKey = sessionContext.sessionKey || daemonConversationLaneKey({
|
package/src/mcp-bridge.js
CHANGED
|
@@ -246,6 +246,36 @@ const tools = [
|
|
|
246
246
|
targetAgent: { type: 'string' },
|
|
247
247
|
}),
|
|
248
248
|
},
|
|
249
|
+
{
|
|
250
|
+
name: 'read_agent_avatar',
|
|
251
|
+
description: 'Read a MagClaw agent avatar image for visual comparison with uploaded attachments.',
|
|
252
|
+
inputSchema: schema({
|
|
253
|
+
targetAgentId: { type: 'string' },
|
|
254
|
+
targetAgent: { type: 'string' },
|
|
255
|
+
maxBytes: { type: 'number' },
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'list_attachments',
|
|
260
|
+
description: 'List MagClaw attachment metadata visible to this agent.',
|
|
261
|
+
inputSchema: schema({
|
|
262
|
+
target: { type: 'string' },
|
|
263
|
+
channel: { type: 'string' },
|
|
264
|
+
workItemId: { type: 'string' },
|
|
265
|
+
messageId: { type: 'string' },
|
|
266
|
+
limit: { type: 'number' },
|
|
267
|
+
}),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'read_attachment',
|
|
271
|
+
description: 'Read an uploaded MagClaw attachment original file. Image attachments are returned as MCP image content when possible.',
|
|
272
|
+
inputSchema: schema({
|
|
273
|
+
attachmentId: { type: 'string' },
|
|
274
|
+
id: { type: 'string' },
|
|
275
|
+
maxBytes: { type: 'number' },
|
|
276
|
+
format: { type: 'string' },
|
|
277
|
+
}),
|
|
278
|
+
},
|
|
249
279
|
{
|
|
250
280
|
name: 'write_memory',
|
|
251
281
|
description: 'Record a concise durable memory for this agent.',
|
|
@@ -358,8 +388,20 @@ function sendError(id, code, message, data = null) {
|
|
|
358
388
|
process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message, data } })}\n`);
|
|
359
389
|
}
|
|
360
390
|
|
|
361
|
-
function
|
|
362
|
-
|
|
391
|
+
function imageResultContent(value = {}) {
|
|
392
|
+
const type = String(value?.avatar?.type || value?.file?.type || value?.attachment?.type || '').toLowerCase();
|
|
393
|
+
if (!type.startsWith('image/')) return null;
|
|
394
|
+
if (value?.file?.truncated || value?.avatar?.truncated) return null;
|
|
395
|
+
const data = String(value?.contentBase64 || '').trim();
|
|
396
|
+
if (!data) return null;
|
|
397
|
+
return { type: 'image', data, mimeType: type };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function contentResult(value) {
|
|
401
|
+
const content = [{ type: 'text', text: jsonText(value) }];
|
|
402
|
+
const image = imageResultContent(value);
|
|
403
|
+
if (image) content.push(image);
|
|
404
|
+
return { content };
|
|
363
405
|
}
|
|
364
406
|
|
|
365
407
|
function jsonText(value) {
|
|
@@ -511,6 +553,33 @@ async function callTool(name, rawArgs = {}) {
|
|
|
511
553
|
targetAgentId: args.targetAgentId || args.targetAgent,
|
|
512
554
|
},
|
|
513
555
|
});
|
|
556
|
+
case 'read_agent_avatar':
|
|
557
|
+
return request('/api/agent-tools/agents/avatar/read', {
|
|
558
|
+
query: {
|
|
559
|
+
agentId: args.agentId,
|
|
560
|
+
targetAgentId: args.targetAgentId || args.targetAgent,
|
|
561
|
+
maxBytes: args.maxBytes || args.max_bytes,
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
case 'list_attachments':
|
|
565
|
+
return request('/api/agent-tools/attachments', {
|
|
566
|
+
query: {
|
|
567
|
+
agentId: args.agentId,
|
|
568
|
+
target: args.target || args.channel,
|
|
569
|
+
workItemId: args.workItemId || args.work_item_id,
|
|
570
|
+
messageId: args.messageId || args.message_id,
|
|
571
|
+
limit: args.limit,
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
case 'read_attachment':
|
|
575
|
+
return request('/api/agent-tools/attachments/read', {
|
|
576
|
+
query: {
|
|
577
|
+
agentId: args.agentId,
|
|
578
|
+
attachmentId: args.attachmentId || args.attachment_id || args.id,
|
|
579
|
+
maxBytes: args.maxBytes || args.max_bytes,
|
|
580
|
+
format: args.format,
|
|
581
|
+
},
|
|
582
|
+
});
|
|
514
583
|
case 'list_tasks':
|
|
515
584
|
return request('/api/agent-tools/tasks', {
|
|
516
585
|
query: {
|
|
@@ -587,7 +656,7 @@ async function handle(message) {
|
|
|
587
656
|
}
|
|
588
657
|
if (message.method === 'tools/call') {
|
|
589
658
|
const result = await callTool(message.params?.name, message.params?.arguments || {});
|
|
590
|
-
send(id,
|
|
659
|
+
send(id, contentResult(result));
|
|
591
660
|
return;
|
|
592
661
|
}
|
|
593
662
|
if (message.method === 'notifications/initialized' || message.method === 'initialized') return;
|