@appoly/multiagent-chat 1.0.6 → 1.0.7
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/main.js +87 -93
- package/package.json +1 -1
- package/renderer.js +7 -1
package/main.js
CHANGED
|
@@ -47,6 +47,13 @@ let agentColors = {}; // Map of agent name -> color
|
|
|
47
47
|
let sessionBaseCommit = null; // Git commit hash at session start for diff baseline
|
|
48
48
|
let currentSessionId = null; // Current active session ID
|
|
49
49
|
|
|
50
|
+
function sendToRenderer(channel, payload) {
|
|
51
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
52
|
+
const wc = mainWindow.webContents;
|
|
53
|
+
if (!wc || wc.isDestroyed()) return;
|
|
54
|
+
wc.send(channel, payload);
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
// ═══════════════════════════════════════════════════════════
|
|
51
58
|
// Session Storage Functions
|
|
52
59
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -538,6 +545,9 @@ function createWindow() {
|
|
|
538
545
|
mainWindow.once('ready-to-show', () => {
|
|
539
546
|
mainWindow.show();
|
|
540
547
|
});
|
|
548
|
+
mainWindow.on('closed', () => {
|
|
549
|
+
mainWindow = null;
|
|
550
|
+
});
|
|
541
551
|
|
|
542
552
|
if (process.platform === 'darwin' && app.dock) {
|
|
543
553
|
app.dock.setIcon(iconPath);
|
|
@@ -743,13 +753,11 @@ class AgentProcess {
|
|
|
743
753
|
this.process.onData((data) => {
|
|
744
754
|
const output = data.toString();
|
|
745
755
|
this.outputBuffer.push(output);
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
});
|
|
752
|
-
}
|
|
756
|
+
sendToRenderer('agent-output', {
|
|
757
|
+
agentName: this.name,
|
|
758
|
+
output: output,
|
|
759
|
+
isPty: true
|
|
760
|
+
});
|
|
753
761
|
});
|
|
754
762
|
|
|
755
763
|
this.process.onExit(({ exitCode, signal }) => {
|
|
@@ -778,17 +786,13 @@ class AgentProcess {
|
|
|
778
786
|
this.process.stdout.on('data', (data) => {
|
|
779
787
|
const output = data.toString();
|
|
780
788
|
this.outputBuffer.push(output);
|
|
781
|
-
|
|
782
|
-
mainWindow.webContents.send('agent-output', { agentName: this.name, output, isPty: false });
|
|
783
|
-
}
|
|
789
|
+
sendToRenderer('agent-output', { agentName: this.name, output, isPty: false });
|
|
784
790
|
});
|
|
785
791
|
|
|
786
792
|
this.process.stderr.on('data', (data) => {
|
|
787
793
|
const output = data.toString();
|
|
788
794
|
this.outputBuffer.push(`[stderr] ${output}`);
|
|
789
|
-
|
|
790
|
-
mainWindow.webContents.send('agent-output', { agentName: this.name, output: `[stderr] ${output}`, isPty: false });
|
|
791
|
-
}
|
|
795
|
+
sendToRenderer('agent-output', { agentName: this.name, output: `[stderr] ${output}`, isPty: false });
|
|
792
796
|
});
|
|
793
797
|
|
|
794
798
|
this.process.on('close', (code) => {
|
|
@@ -809,13 +813,11 @@ class AgentProcess {
|
|
|
809
813
|
handleExit(exitCode) {
|
|
810
814
|
if (this.intentionalStop) {
|
|
811
815
|
console.log(`Agent ${this.name} stopped intentionally`);
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
});
|
|
818
|
-
}
|
|
816
|
+
sendToRenderer('agent-status', {
|
|
817
|
+
agentName: this.name,
|
|
818
|
+
status: 'stopped',
|
|
819
|
+
exitCode: exitCode
|
|
820
|
+
});
|
|
819
821
|
return;
|
|
820
822
|
}
|
|
821
823
|
|
|
@@ -824,47 +826,39 @@ class AgentProcess {
|
|
|
824
826
|
this.restartCount++;
|
|
825
827
|
console.log(`Agent ${this.name} exited unexpectedly (code ${exitCode}), restarting (attempt ${this.restartCount}/${this.maxRestarts})...`);
|
|
826
828
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
});
|
|
834
|
-
}
|
|
829
|
+
sendToRenderer('agent-status', {
|
|
830
|
+
agentName: this.name,
|
|
831
|
+
status: 'restarting',
|
|
832
|
+
exitCode: exitCode,
|
|
833
|
+
restartCount: this.restartCount
|
|
834
|
+
});
|
|
835
835
|
|
|
836
836
|
// Wait a moment before relaunching (give time for auto-updates etc.)
|
|
837
837
|
const delay = 2000 * this.restartCount;
|
|
838
838
|
setTimeout(() => {
|
|
839
839
|
this.start(this.lastPrompt).then(() => {
|
|
840
840
|
console.log(`Agent ${this.name} restarted successfully (attempt ${this.restartCount})`);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
});
|
|
846
|
-
}
|
|
841
|
+
sendToRenderer('agent-status', {
|
|
842
|
+
agentName: this.name,
|
|
843
|
+
status: 'running'
|
|
844
|
+
});
|
|
847
845
|
}).catch(err => {
|
|
848
846
|
console.error(`Failed to restart agent ${this.name}:`, err);
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
});
|
|
856
|
-
}
|
|
847
|
+
sendToRenderer('agent-status', {
|
|
848
|
+
agentName: this.name,
|
|
849
|
+
status: 'stopped',
|
|
850
|
+
exitCode: exitCode,
|
|
851
|
+
error: `Restart failed: ${err.message}`
|
|
852
|
+
});
|
|
857
853
|
});
|
|
858
854
|
}, delay);
|
|
859
855
|
} else {
|
|
860
856
|
console.log(`Agent ${this.name} exited (code ${exitCode}), max restarts reached or no prompt stored`);
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
});
|
|
867
|
-
}
|
|
857
|
+
sendToRenderer('agent-status', {
|
|
858
|
+
agentName: this.name,
|
|
859
|
+
status: 'stopped',
|
|
860
|
+
exitCode: exitCode
|
|
861
|
+
});
|
|
868
862
|
}
|
|
869
863
|
}
|
|
870
864
|
|
|
@@ -918,10 +912,11 @@ function getOutboxRelativePath(agentName) {
|
|
|
918
912
|
}
|
|
919
913
|
|
|
920
914
|
function sendMessageToOtherAgents(senderName, message) {
|
|
915
|
+
const timestamp = new Date().toISOString();
|
|
921
916
|
for (const agent of agents) {
|
|
922
917
|
if (agent.name.toLowerCase() !== senderName.toLowerCase()) {
|
|
923
918
|
const outboxFile = getOutboxRelativePath(agent.name);
|
|
924
|
-
const formattedMessage = `\n---\n📨 MESSAGE FROM ${senderName.toUpperCase()}:\n\n${message}\n\n---\n(Respond via: cat << 'EOF' > ${outboxFile})\n`;
|
|
919
|
+
const formattedMessage = `\n---\n📨 MESSAGE FROM ${senderName.toUpperCase()}:\n🕐 ${timestamp}\n\n${message}\n\n---\n(Respond via: cat << 'EOF' > ${outboxFile})\n`;
|
|
925
920
|
console.log(`Delivering message from ${senderName} to ${agent.name}`);
|
|
926
921
|
agent.sendMessage(formattedMessage);
|
|
927
922
|
}
|
|
@@ -929,9 +924,10 @@ function sendMessageToOtherAgents(senderName, message) {
|
|
|
929
924
|
}
|
|
930
925
|
|
|
931
926
|
function sendMessageToAllAgents(message) {
|
|
927
|
+
const timestamp = new Date().toISOString();
|
|
932
928
|
for (const agent of agents) {
|
|
933
929
|
const outboxFile = getOutboxRelativePath(agent.name);
|
|
934
|
-
const formattedMessage = `\n---\n📨 MESSAGE FROM USER:\n\n${message}\n\n---\n(Respond via: cat << 'EOF' > ${outboxFile})\n`;
|
|
930
|
+
const formattedMessage = `\n---\n📨 MESSAGE FROM USER:\n🕐 ${timestamp}\n\n${message}\n\n---\n(Respond via: cat << 'EOF' > ${outboxFile})\n`;
|
|
935
931
|
console.log(`Delivering user message to ${agent.name}`);
|
|
936
932
|
agent.sendMessage(formattedMessage);
|
|
937
933
|
}
|
|
@@ -963,21 +959,17 @@ async function startAgents(challenge) {
|
|
|
963
959
|
await agent.start(prompt);
|
|
964
960
|
console.log(`Started agent: ${agent.name}`);
|
|
965
961
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
});
|
|
971
|
-
}
|
|
962
|
+
sendToRenderer('agent-status', {
|
|
963
|
+
agentName: agent.name,
|
|
964
|
+
status: 'running'
|
|
965
|
+
});
|
|
972
966
|
} catch (error) {
|
|
973
967
|
console.error(`Failed to start agent ${agent.name}:`, error);
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
});
|
|
980
|
-
}
|
|
968
|
+
sendToRenderer('agent-status', {
|
|
969
|
+
agentName: agent.name,
|
|
970
|
+
status: 'error',
|
|
971
|
+
error: error.message
|
|
972
|
+
});
|
|
981
973
|
}
|
|
982
974
|
}
|
|
983
975
|
}
|
|
@@ -997,9 +989,7 @@ function startFileWatcher() {
|
|
|
997
989
|
fileWatcher.on('change', async () => {
|
|
998
990
|
try {
|
|
999
991
|
const messages = await getChatContent();
|
|
1000
|
-
|
|
1001
|
-
mainWindow.webContents.send('chat-updated', messages);
|
|
1002
|
-
}
|
|
992
|
+
sendToRenderer('chat-updated', messages);
|
|
1003
993
|
} catch (error) {
|
|
1004
994
|
console.error('Error reading chat file:', error);
|
|
1005
995
|
}
|
|
@@ -1066,9 +1056,7 @@ function startOutboxWatcher() {
|
|
|
1066
1056
|
}).catch(e => console.warn('Failed to update session index:', e.message));
|
|
1067
1057
|
}
|
|
1068
1058
|
|
|
1069
|
-
|
|
1070
|
-
mainWindow.webContents.send('chat-message', message);
|
|
1071
|
-
}
|
|
1059
|
+
sendToRenderer('chat-message', message);
|
|
1072
1060
|
} catch (error) {
|
|
1073
1061
|
console.error(`Error processing outbox file ${filePath}:`, error);
|
|
1074
1062
|
} finally {
|
|
@@ -1118,9 +1106,7 @@ async function sendUserMessage(messageText) {
|
|
|
1118
1106
|
}).catch(e => console.warn('Failed to update session index:', e.message));
|
|
1119
1107
|
}
|
|
1120
1108
|
|
|
1121
|
-
|
|
1122
|
-
mainWindow.webContents.send('chat-message', message);
|
|
1123
|
-
}
|
|
1109
|
+
sendToRenderer('chat-message', message);
|
|
1124
1110
|
} catch (error) {
|
|
1125
1111
|
console.error('Error appending user message:', error);
|
|
1126
1112
|
throw error;
|
|
@@ -1373,21 +1359,17 @@ ipcMain.handle('resume-session', async (event, { projectRoot, sessionId, newMess
|
|
|
1373
1359
|
await agent.start(prompt);
|
|
1374
1360
|
console.log(`Started agent: ${agent.name} (resumed)`);
|
|
1375
1361
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1362
|
+
sendToRenderer('agent-status', {
|
|
1363
|
+
agentName: agent.name,
|
|
1364
|
+
status: 'running'
|
|
1365
|
+
});
|
|
1382
1366
|
} catch (error) {
|
|
1383
1367
|
console.error(`Failed to start agent ${agent.name}:`, error);
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1368
|
+
sendToRenderer('agent-status', {
|
|
1369
|
+
agentName: agent.name,
|
|
1370
|
+
status: 'error',
|
|
1371
|
+
error: error.message
|
|
1372
|
+
});
|
|
1391
1373
|
}
|
|
1392
1374
|
}
|
|
1393
1375
|
|
|
@@ -1436,6 +1418,20 @@ ipcMain.handle('start-session', async (event, { challenge, workspace: selectedWo
|
|
|
1436
1418
|
currentSessionId = sessionId;
|
|
1437
1419
|
const sessionDir = await setupSessionDirectory(resolvedRoot, sessionId);
|
|
1438
1420
|
workspacePath = sessionDir;
|
|
1421
|
+
const initialTimestamp = new Date().toISOString();
|
|
1422
|
+
|
|
1423
|
+
// Seed chat history with the initial user challenge so it appears in UI immediately.
|
|
1424
|
+
messageSequence = 1;
|
|
1425
|
+
const initialMessage = {
|
|
1426
|
+
seq: messageSequence,
|
|
1427
|
+
type: 'user',
|
|
1428
|
+
agent: 'User',
|
|
1429
|
+
timestamp: initialTimestamp,
|
|
1430
|
+
content: challenge,
|
|
1431
|
+
color: agentColors['user'] || '#a0aec0'
|
|
1432
|
+
};
|
|
1433
|
+
const chatPath = path.join(workspacePath, config.chat_file || 'chat.jsonl');
|
|
1434
|
+
await fs.appendFile(chatPath, JSON.stringify(initialMessage) + '\n');
|
|
1439
1435
|
|
|
1440
1436
|
// Add to session index
|
|
1441
1437
|
const sessionMeta = {
|
|
@@ -1443,16 +1439,13 @@ ipcMain.handle('start-session', async (event, { challenge, workspace: selectedWo
|
|
|
1443
1439
|
title: generateSessionTitle(challenge),
|
|
1444
1440
|
firstPrompt: challenge.slice(0, 200),
|
|
1445
1441
|
workspace: resolvedRoot,
|
|
1446
|
-
createdAt:
|
|
1447
|
-
lastActiveAt:
|
|
1448
|
-
messageCount:
|
|
1442
|
+
createdAt: initialTimestamp,
|
|
1443
|
+
lastActiveAt: initialTimestamp,
|
|
1444
|
+
messageCount: 1,
|
|
1449
1445
|
status: 'active'
|
|
1450
1446
|
};
|
|
1451
1447
|
await addSessionToIndex(resolvedRoot, sessionMeta);
|
|
1452
1448
|
|
|
1453
|
-
// Reset message sequence
|
|
1454
|
-
messageSequence = 0;
|
|
1455
|
-
|
|
1456
1449
|
initializeAgents();
|
|
1457
1450
|
await startAgents(challenge);
|
|
1458
1451
|
startFileWatcher();
|
|
@@ -1466,7 +1459,8 @@ ipcMain.handle('start-session', async (event, { challenge, workspace: selectedWo
|
|
|
1466
1459
|
agents: agents.map(a => ({ name: a.name, use_pty: a.use_pty })),
|
|
1467
1460
|
workspace: agentCwd,
|
|
1468
1461
|
colors: agentColors,
|
|
1469
|
-
sessionId: sessionId
|
|
1462
|
+
sessionId: sessionId,
|
|
1463
|
+
initialMessage
|
|
1470
1464
|
};
|
|
1471
1465
|
} catch (error) {
|
|
1472
1466
|
console.error('Error starting session:', error);
|
package/package.json
CHANGED
package/renderer.js
CHANGED
|
@@ -311,7 +311,13 @@ async function handleNewSession(challenge) {
|
|
|
311
311
|
if (result.success) {
|
|
312
312
|
currentSessionId = result.sessionId;
|
|
313
313
|
agentColors = result.colors || {};
|
|
314
|
-
|
|
314
|
+
if (result.initialMessage) {
|
|
315
|
+
chatMessages = [result.initialMessage];
|
|
316
|
+
} else if (Array.isArray(result.messages)) {
|
|
317
|
+
chatMessages = result.messages;
|
|
318
|
+
} else {
|
|
319
|
+
chatMessages = [];
|
|
320
|
+
}
|
|
315
321
|
sessionState = 'active';
|
|
316
322
|
|
|
317
323
|
result.agents.forEach(agent => {
|