@agent-link/agent 0.1.211 → 0.1.213
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/dist/claude.js +18 -20
- package/dist/claude.js.map +1 -1
- package/dist/connection.d.ts +0 -11
- package/dist/connection.js +110 -281
- package/dist/connection.js.map +1 -1
- package/dist/directory-handlers.d.ts +0 -7
- package/dist/directory-handlers.js +6 -12
- package/dist/directory-handlers.js.map +1 -1
- package/dist/history.d.ts +0 -15
- package/dist/history.js +0 -49
- package/dist/history.js.map +1 -1
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +0 -30
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { loadRuntimeState, saveRuntimeState, getBrainDataDir, getConversationPermission, setConversationPermission, clearConversationPermission, getOrCreateE2EKey, encodeBase64Url } from './config.js';
|
|
6
|
-
import { handleListDirectory, handleReadFile, handleChangeWorkDir, handleUpdateFile, handleCreateFile, handleCreateDirectory, handleDeleteFile, handleUploadFile, handleUploadFileChunk, handleDownloadFile, handleDownloadChunkAck
|
|
6
|
+
import { handleListDirectory, handleReadFile, handleChangeWorkDir, handleUpdateFile, handleCreateFile, handleCreateDirectory, handleDeleteFile, handleUploadFile, handleUploadFileChunk, handleDownloadFile, handleDownloadChunkAck } from './directory-handlers.js';
|
|
7
7
|
import { handleGitStatus, handleGitDiff, handleGitStage, handleGitUnstage, handleGitDiscard, handleGitCommit, handleGitWorktreeList, handleGitStash, handleGitPull, handleGitPush, handleGitDeleteFile, resolveToRepoRoot } from './git-handlers.js';
|
|
8
8
|
import { handleCreateTeam, handleDissolveTeam, handleListTeams, handleGetTeam, handleGetTeamAgentHistory, handleDeleteTeam, handleRenameTeam } from './team-handlers.js';
|
|
9
9
|
import { handleCreateLoop, handleUpdateLoop, handleDeleteLoop, handleListLoops, handleGetLoop, handleRunLoop, handleCancelLoopExecution, handleListLoopExecutions, handleGetLoopExecutionMessages, handleQueryLoopStatus } from './loop-handlers.js';
|
|
@@ -20,7 +20,7 @@ import { createTerminalManager } from './terminal.js';
|
|
|
20
20
|
const require = createRequire(import.meta.url);
|
|
21
21
|
const pkg = require('../package.json');
|
|
22
22
|
import { handleChat as claudeHandleChat, addSendFn, abort as abortClaude, abortAll as abortAllClaude, cancelExecution as claudeCancelExecution, handleBtwQuestion, getConversation, getConversations, clearSessionId, evictByClaudeSessionId, rebindConversation, addOutputObserver, removeOutputObserver, addCloseObserver, removeCloseObserver, setOutputObserver, clearOutputObserver, setCloseObserver, clearCloseObserver, getPendingQuestions, getPendingToolPermissions, addOnSessionStarted, getBackend, AgentRuntime } from './runtime.js';
|
|
23
|
-
import { listAllRecentSessions, getSessionCacheStats
|
|
23
|
+
import { listAllRecentSessions, getSessionCacheStats } from './history.js';
|
|
24
24
|
import { searchSessions } from './search-sessions.js';
|
|
25
25
|
import { listMemoryFiles, updateMemoryFile, deleteMemoryFile } from './memory.js';
|
|
26
26
|
import { decodeKey, parseMessage, encryptAndSend, e2eEncrypt, e2eDecrypt, isE2EEncrypted } from './encryption.js';
|
|
@@ -34,25 +34,6 @@ const RECONNECT_BASE_DELAY = 1000;
|
|
|
34
34
|
const RECONNECT_MAX_DELAY = 10_000;
|
|
35
35
|
const HEARTBEAT_INTERVAL = 45_000; // Send ping every 45s (staggered from server's 30s)
|
|
36
36
|
const HEARTBEAT_TIMEOUT = 15_000; // Max wait for pong before declaring dead
|
|
37
|
-
/**
|
|
38
|
-
* Resolve the workDir for an inbound message. If the message carries a
|
|
39
|
-
* `conversationId` and the runtime has a per-conv workDir mapped for it,
|
|
40
|
-
* use that; otherwise fall back to the agent-global `state.workDir`.
|
|
41
|
-
*/
|
|
42
|
-
function resolveWorkDir(msg, st, rt) {
|
|
43
|
-
const convId = msg?.conversationId;
|
|
44
|
-
if (typeof convId === 'string' && convId && rt) {
|
|
45
|
-
const perConv = rt.getConversationWorkDir(convId);
|
|
46
|
-
if (perConv)
|
|
47
|
-
return perConv;
|
|
48
|
-
}
|
|
49
|
-
return st.workDir;
|
|
50
|
-
}
|
|
51
|
-
function tag(sendFn, convId) {
|
|
52
|
-
if (!convId)
|
|
53
|
-
return sendFn;
|
|
54
|
-
return (m) => sendFn({ ...m, conversationId: convId });
|
|
55
|
-
}
|
|
56
37
|
const state = {
|
|
57
38
|
ws: null,
|
|
58
39
|
sessionId: null,
|
|
@@ -420,19 +401,7 @@ export function disconnect() {
|
|
|
420
401
|
return r ? r.shutdown() : Promise.resolve();
|
|
421
402
|
}
|
|
422
403
|
let sendQueue = Promise.resolve();
|
|
423
|
-
/** Test-only override: when set, send() routes through this fn instead of WebSocket. */
|
|
424
|
-
let testSendOverride = null;
|
|
425
|
-
export function _setTestSendOverride(fn) {
|
|
426
|
-
testSendOverride = fn;
|
|
427
|
-
}
|
|
428
404
|
export function send(msg) {
|
|
429
|
-
if (testSendOverride) {
|
|
430
|
-
try {
|
|
431
|
-
testSendOverride(msg);
|
|
432
|
-
}
|
|
433
|
-
catch { /* swallow in tests */ }
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
405
|
if (state.ws && state.ws.readyState === WebSocket.OPEN) {
|
|
437
406
|
sendQueue = sendQueue.then(async () => {
|
|
438
407
|
if (!state.ws || state.ws.readyState !== WebSocket.OPEN)
|
|
@@ -471,9 +440,7 @@ function buildWsUrl(config) {
|
|
|
471
440
|
name: config.name,
|
|
472
441
|
workDir: state.workDir,
|
|
473
442
|
hostname: os.hostname(),
|
|
474
|
-
|
|
475
|
-
// so the web's MIN_AGENT_VERSION_PER_CONV_WORKDIR gate triggers fallback.
|
|
476
|
-
version: process.env.AGENTLINK_FAKE_VERSION || pkg.version,
|
|
443
|
+
version: pkg.version,
|
|
477
444
|
});
|
|
478
445
|
// On reconnect, send previous sessionId so the URL stays valid
|
|
479
446
|
if (state.sessionId) {
|
|
@@ -515,21 +482,6 @@ const BRAIN_SERVER = 'http://localhost:8001';
|
|
|
515
482
|
// Pending action item IDs: conversationId → actionItemId
|
|
516
483
|
// Used to link a chat's Claude session to an action item for CLI start
|
|
517
484
|
const pendingActionItems = new Map();
|
|
518
|
-
/** Test-only: invoke the dispatch directly. */
|
|
519
|
-
export function _handleServerMessageForTests(msg) {
|
|
520
|
-
handleServerMessage(msg);
|
|
521
|
-
}
|
|
522
|
-
/** Test-only: read/write the connection-level workDir state. */
|
|
523
|
-
export function _setStateWorkDirForTests(wd) {
|
|
524
|
-
state.workDir = wd;
|
|
525
|
-
}
|
|
526
|
-
export function _getStateWorkDirForTests() {
|
|
527
|
-
return state.workDir;
|
|
528
|
-
}
|
|
529
|
-
/** Test-only: install a runtime instance (bypasses connect()). */
|
|
530
|
-
export function _setRuntimeForTests(rt) {
|
|
531
|
-
runtime = rt;
|
|
532
|
-
}
|
|
533
485
|
function handleServerMessage(msg) {
|
|
534
486
|
console.log(`[AgentLink] ← ${msg.type}`);
|
|
535
487
|
switch (msg.type) {
|
|
@@ -600,7 +552,7 @@ function handleServerMessage(msg) {
|
|
|
600
552
|
const chatDir = explicitWorkDir
|
|
601
553
|
?? existingConv?.workDir
|
|
602
554
|
?? ((recapId || briefingDate || devopsEntityType || projectName || icmId) ? BRAIN_DATA_DIR : null)
|
|
603
|
-
??
|
|
555
|
+
?? state.workDir;
|
|
604
556
|
// Track actionItemId for linking to Claude session on session_started
|
|
605
557
|
if (actionItemId && chatConvId) {
|
|
606
558
|
pendingActionItems.set(chatConvId, actionItemId);
|
|
@@ -621,18 +573,12 @@ function handleServerMessage(msg) {
|
|
|
621
573
|
console.warn('[AgentLink] chat received after runtime shutdown — ignoring');
|
|
622
574
|
break;
|
|
623
575
|
}
|
|
624
|
-
// Seed per-conv workDir map so subsequent resolveWorkDir calls for this
|
|
625
|
-
// conversation see the same dir (no eviction — this is the active turn).
|
|
626
|
-
runtime.setConversationWorkDir(startConvId, chatDir);
|
|
627
576
|
runtime.startTurn(startConvId, {
|
|
628
577
|
text: msg.prompt,
|
|
629
578
|
files: msg.files,
|
|
630
579
|
metadata: chatOptions,
|
|
631
580
|
}, { workDir: chatDir, resumeSessionId: chatOptions.resumeSessionId }).catch((err) => {
|
|
632
|
-
|
|
633
|
-
console.error('[AgentLink] runtime.startTurn failed:', message);
|
|
634
|
-
send({ type: 'error', conversationId: startConvId, message: `Failed to start turn: ${message}` });
|
|
635
|
-
send({ type: 'turn_completed', conversationId: startConvId });
|
|
581
|
+
console.error('[AgentLink] runtime.startTurn failed:', err.message);
|
|
636
582
|
});
|
|
637
583
|
break;
|
|
638
584
|
}
|
|
@@ -648,18 +594,12 @@ function handleServerMessage(msg) {
|
|
|
648
594
|
});
|
|
649
595
|
break;
|
|
650
596
|
}
|
|
651
|
-
case 'list_sessions':
|
|
652
|
-
|
|
653
|
-
const wd = resolveWorkDir(msg, state, runtime);
|
|
654
|
-
handleListSessions(wd, m.conversationId).catch((err) => {
|
|
597
|
+
case 'list_sessions':
|
|
598
|
+
handleListSessions().catch((err) => {
|
|
655
599
|
console.error('[AgentLink] handleListSessions failed:', err);
|
|
656
|
-
|
|
657
|
-
if (m.conversationId)
|
|
658
|
-
out.conversationId = m.conversationId;
|
|
659
|
-
send(out);
|
|
600
|
+
send({ type: 'sessions_list', sessions: [], workDir: state.workDir });
|
|
660
601
|
});
|
|
661
602
|
break;
|
|
662
|
-
}
|
|
663
603
|
case 'list_recent_sessions':
|
|
664
604
|
handleListRecentSessions(msg);
|
|
665
605
|
break;
|
|
@@ -669,102 +609,56 @@ function handleServerMessage(msg) {
|
|
|
669
609
|
case 'cancel_search':
|
|
670
610
|
handleCancelSearch(msg);
|
|
671
611
|
break;
|
|
672
|
-
case 'list_directory':
|
|
673
|
-
|
|
674
|
-
handleListDirectory(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
612
|
+
case 'list_directory':
|
|
613
|
+
handleListDirectory(msg, state.workDir, send);
|
|
675
614
|
break;
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const m = msg;
|
|
679
|
-
handleReadFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
615
|
+
case 'read_file':
|
|
616
|
+
handleReadFile(msg, state.workDir, send);
|
|
680
617
|
break;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const m = msg;
|
|
684
|
-
const tagged = tag(send, m.conversationId);
|
|
685
|
-
resolveToRepoRoot(m, resolveWorkDir(msg, state, runtime))
|
|
618
|
+
case 'git_read_file':
|
|
619
|
+
resolveToRepoRoot(msg, state.workDir)
|
|
686
620
|
.then((repoRoot) => {
|
|
687
|
-
const filePath =
|
|
621
|
+
const filePath = msg.filePath;
|
|
688
622
|
// Security: reject absolute paths and .. traversal
|
|
689
623
|
if (path.isAbsolute(filePath) || filePath.includes('..')) {
|
|
690
|
-
|
|
624
|
+
send({ type: 'file_content', filePath, error: 'Invalid file path' });
|
|
691
625
|
return;
|
|
692
626
|
}
|
|
693
627
|
const resolved = path.resolve(repoRoot, filePath);
|
|
694
628
|
if (!resolved.startsWith(repoRoot)) {
|
|
695
|
-
|
|
629
|
+
send({ type: 'file_content', filePath, error: 'Invalid file path' });
|
|
696
630
|
return;
|
|
697
631
|
}
|
|
698
|
-
handleReadFile({ filePath }, repoRoot,
|
|
632
|
+
handleReadFile({ filePath }, repoRoot, send);
|
|
699
633
|
});
|
|
700
634
|
break;
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const m = msg;
|
|
704
|
-
handleUpdateFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
635
|
+
case 'update_file':
|
|
636
|
+
handleUpdateFile(msg, state.workDir, send);
|
|
705
637
|
break;
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const m = msg;
|
|
709
|
-
handleCreateFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
638
|
+
case 'create_file':
|
|
639
|
+
handleCreateFile(msg, state.workDir, send);
|
|
710
640
|
break;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const m = msg;
|
|
714
|
-
handleCreateDirectory(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
641
|
+
case 'create_directory':
|
|
642
|
+
handleCreateDirectory(msg, state.workDir, send);
|
|
715
643
|
break;
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const m = msg;
|
|
719
|
-
handleDeleteFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
644
|
+
case 'delete_file':
|
|
645
|
+
handleDeleteFile(msg, state.workDir, send);
|
|
720
646
|
break;
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const m = msg;
|
|
724
|
-
handleUploadFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
647
|
+
case 'upload_file':
|
|
648
|
+
handleUploadFile(msg, state.workDir, send);
|
|
725
649
|
break;
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const m = msg;
|
|
729
|
-
handleUploadFileChunk(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
650
|
+
case 'upload_file_chunk':
|
|
651
|
+
handleUploadFileChunk(msg, state.workDir, send);
|
|
730
652
|
break;
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const m = msg;
|
|
734
|
-
handleDownloadFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
653
|
+
case 'download_file':
|
|
654
|
+
handleDownloadFile(msg, state.workDir, send);
|
|
735
655
|
break;
|
|
736
|
-
}
|
|
737
656
|
case 'download_chunk_ack':
|
|
738
657
|
handleDownloadChunkAck(msg);
|
|
739
658
|
break;
|
|
740
659
|
case 'change_workdir':
|
|
741
|
-
handleChangeWorkDir(msg, state, send,
|
|
742
|
-
handleListSessions(state.workDir).catch((err) => {
|
|
743
|
-
console.error('[AgentLink] handleListSessions failed:', err);
|
|
744
|
-
send({ type: 'sessions_list', sessions: [], workDir: state.workDir });
|
|
745
|
-
});
|
|
746
|
-
});
|
|
747
|
-
break;
|
|
748
|
-
case 'set_conversation_workdir': {
|
|
749
|
-
const m = msg;
|
|
750
|
-
const convId = m.conversationId;
|
|
751
|
-
if (typeof convId !== 'string' || !convId) {
|
|
752
|
-
send({ type: 'error', message: 'set_conversation_workdir requires conversationId' });
|
|
753
|
-
break;
|
|
754
|
-
}
|
|
755
|
-
const v = validateWorkDir(m.workDir);
|
|
756
|
-
if (!v.ok) {
|
|
757
|
-
send({ type: 'error', conversationId: convId, message: v.error });
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
760
|
-
runtime?.setConversationWorkDir(convId, v.resolved, { evictSession: true });
|
|
761
|
-
// Also update the global default so NEW conversations (with a fresh
|
|
762
|
-
// convId that has no per-conv mapping) inherit the user's latest
|
|
763
|
-
// directory choice instead of falling back to the agent startup dir.
|
|
764
|
-
state.workDir = v.resolved;
|
|
765
|
-
send({ type: 'conversation_workdir_changed', conversationId: convId, workDir: v.resolved });
|
|
660
|
+
handleChangeWorkDir(msg, state, send, handleListSessions);
|
|
766
661
|
break;
|
|
767
|
-
}
|
|
768
662
|
case 'new_conversation':
|
|
769
663
|
// Backward compat: old web client sends this to reset the single conversation
|
|
770
664
|
abortClaude();
|
|
@@ -795,18 +689,10 @@ function handleServerMessage(msg) {
|
|
|
795
689
|
// making the entire switch dispatch async. .catch() ensures backend
|
|
796
690
|
// rejections don't become unhandled promise rejections.
|
|
797
691
|
(async () => {
|
|
798
|
-
// Try current workDir first; fall back to BRAIN_DATA_DIR for recap/briefing chat sessions
|
|
799
|
-
|
|
800
|
-
// resume_conversation message with conversationId (see outboundTag.js).
|
|
801
|
-
const initialWorkDir = resolveWorkDir(msg, state, runtime);
|
|
802
|
-
// Deeplink fix: when the web client opens a URL like #/chat/<sessionId>
|
|
803
|
-
// in a fresh tab, it has no projectPath context, so resolveWorkDir
|
|
804
|
-
// returns the agent's startup workDir — which may be wrong for this
|
|
805
|
-
// session. Prefer the authoritative `cwd` recorded in the JSONL itself.
|
|
806
|
-
const jsonlCwd = await findSessionProjectPath(m.claudeSessionId);
|
|
807
|
-
let resolvedWorkDir = jsonlCwd || initialWorkDir;
|
|
692
|
+
// Try current workDir first; fall back to BRAIN_DATA_DIR for recap/briefing chat sessions
|
|
693
|
+
let resolvedWorkDir = state.workDir;
|
|
808
694
|
let history = runtime
|
|
809
|
-
? await runtime.readHistory(
|
|
695
|
+
? await runtime.readHistory(state.workDir, m.claudeSessionId)
|
|
810
696
|
: [];
|
|
811
697
|
if (history.length === 0) {
|
|
812
698
|
const sessionMeta_ = loadSessionMetadata(m.claudeSessionId);
|
|
@@ -867,13 +753,8 @@ function handleServerMessage(msg) {
|
|
|
867
753
|
// process's own session id).
|
|
868
754
|
const seedCid = convId ?? 'default';
|
|
869
755
|
const seedConv = currentConv;
|
|
870
|
-
if (!seedConv?.turnActive
|
|
871
|
-
|
|
872
|
-
// The web client re-requests list_sessions immediately after receiving
|
|
873
|
-
// conversation_resumed (when workDir changed), and list_sessions uses
|
|
874
|
-
// resolveWorkDir which reads from this per-conv map. Without awaiting,
|
|
875
|
-
// the map is still empty and resolveWorkDir falls back to startup dir.
|
|
876
|
-
await runtime.ensureConversation(seedCid, {
|
|
756
|
+
if (!seedConv?.turnActive) {
|
|
757
|
+
runtime?.ensureConversation(seedCid, {
|
|
877
758
|
workDir: resolvedWorkDir,
|
|
878
759
|
resumeSessionId: m.claudeSessionId,
|
|
879
760
|
}).catch(() => { });
|
|
@@ -882,7 +763,6 @@ function handleServerMessage(msg) {
|
|
|
882
763
|
type: 'conversation_resumed',
|
|
883
764
|
conversationId: convId,
|
|
884
765
|
claudeSessionId: m.claudeSessionId,
|
|
885
|
-
workDir: resolvedWorkDir,
|
|
886
766
|
history,
|
|
887
767
|
isCompacting: isSameSession && (runtime?.isCompacting(convId) ?? false),
|
|
888
768
|
isProcessing: isSameSession && currentConv?.turnActive === true,
|
|
@@ -934,23 +814,17 @@ function handleServerMessage(msg) {
|
|
|
934
814
|
}
|
|
935
815
|
case 'delete_session': {
|
|
936
816
|
const m = msg;
|
|
937
|
-
handleDeleteSession(m.sessionId
|
|
817
|
+
handleDeleteSession(m.sessionId).catch((err) => {
|
|
938
818
|
console.error('[AgentLink] handleDeleteSession failed:', err);
|
|
939
|
-
|
|
940
|
-
if (m.conversationId)
|
|
941
|
-
out.conversationId = m.conversationId;
|
|
942
|
-
send(out);
|
|
819
|
+
send({ type: 'error', message: 'Session not found or could not be deleted.' });
|
|
943
820
|
});
|
|
944
821
|
break;
|
|
945
822
|
}
|
|
946
823
|
case 'rename_session': {
|
|
947
824
|
const m = msg;
|
|
948
|
-
handleRenameSession(m.sessionId, m.newTitle
|
|
825
|
+
handleRenameSession(m.sessionId, m.newTitle).catch((err) => {
|
|
949
826
|
console.error('[AgentLink] handleRenameSession failed:', err);
|
|
950
|
-
|
|
951
|
-
if (m.conversationId)
|
|
952
|
-
out.conversationId = m.conversationId;
|
|
953
|
-
send(out);
|
|
827
|
+
send({ type: 'error', message: 'Session not found or could not be renamed.' });
|
|
954
828
|
});
|
|
955
829
|
break;
|
|
956
830
|
}
|
|
@@ -991,19 +865,15 @@ function handleServerMessage(msg) {
|
|
|
991
865
|
});
|
|
992
866
|
break;
|
|
993
867
|
}
|
|
994
|
-
case 'create_team':
|
|
995
|
-
|
|
996
|
-
handleCreateTeam(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
868
|
+
case 'create_team':
|
|
869
|
+
handleCreateTeam(msg, state.workDir, send);
|
|
997
870
|
break;
|
|
998
|
-
}
|
|
999
871
|
case 'dissolve_team':
|
|
1000
872
|
handleDissolveTeam();
|
|
1001
873
|
break;
|
|
1002
|
-
case 'list_teams':
|
|
1003
|
-
|
|
1004
|
-
handleListTeams(resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
874
|
+
case 'list_teams':
|
|
875
|
+
handleListTeams(state.workDir, send);
|
|
1005
876
|
break;
|
|
1006
|
-
}
|
|
1007
877
|
case 'get_team':
|
|
1008
878
|
handleGetTeam(msg, send);
|
|
1009
879
|
break;
|
|
@@ -1017,22 +887,18 @@ function handleServerMessage(msg) {
|
|
|
1017
887
|
handleRenameTeam(msg, send);
|
|
1018
888
|
break;
|
|
1019
889
|
// ── Loop (Scheduled Tasks) handlers ────────────────────────────────
|
|
1020
|
-
case 'create_loop':
|
|
1021
|
-
|
|
1022
|
-
handleCreateLoop(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
890
|
+
case 'create_loop':
|
|
891
|
+
handleCreateLoop(msg, state.workDir, send);
|
|
1023
892
|
break;
|
|
1024
|
-
}
|
|
1025
893
|
case 'update_loop':
|
|
1026
894
|
handleUpdateLoop(msg, send);
|
|
1027
895
|
break;
|
|
1028
896
|
case 'delete_loop':
|
|
1029
897
|
handleDeleteLoop(msg, send);
|
|
1030
898
|
break;
|
|
1031
|
-
case 'list_loops':
|
|
1032
|
-
|
|
1033
|
-
handleListLoops(m.showAll ? undefined : resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
899
|
+
case 'list_loops':
|
|
900
|
+
handleListLoops(msg.showAll ? undefined : state.workDir, send);
|
|
1034
901
|
break;
|
|
1035
|
-
}
|
|
1036
902
|
case 'get_loop':
|
|
1037
903
|
handleGetLoop(msg, send);
|
|
1038
904
|
break;
|
|
@@ -1055,26 +921,25 @@ function handleServerMessage(msg) {
|
|
|
1055
921
|
send({ type: 'pong', ts: msg.ts });
|
|
1056
922
|
break;
|
|
1057
923
|
case 'list_memory': {
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
tag(send, m.conversationId)({ type: 'memory_list', memoryDir: result.memoryDir, files: result.files });
|
|
924
|
+
const result = listMemoryFiles(state.workDir);
|
|
925
|
+
send({ type: 'memory_list', memoryDir: result.memoryDir, files: result.files });
|
|
1061
926
|
break;
|
|
1062
927
|
}
|
|
1063
928
|
case 'update_memory': {
|
|
1064
|
-
const { filename, content
|
|
1065
|
-
const result = updateMemoryFile(
|
|
1066
|
-
|
|
929
|
+
const { filename, content } = msg;
|
|
930
|
+
const result = updateMemoryFile(state.workDir, filename, content);
|
|
931
|
+
send({ type: 'memory_updated', filename, ...result });
|
|
1067
932
|
break;
|
|
1068
933
|
}
|
|
1069
934
|
case 'delete_memory': {
|
|
1070
|
-
const { filename
|
|
1071
|
-
const result = deleteMemoryFile(
|
|
1072
|
-
|
|
935
|
+
const { filename } = msg;
|
|
936
|
+
const result = deleteMemoryFile(state.workDir, filename);
|
|
937
|
+
send({ type: 'memory_deleted', filename, ...result });
|
|
1073
938
|
break;
|
|
1074
939
|
}
|
|
1075
940
|
case 'btw_question': {
|
|
1076
941
|
const { question, conversationId, claudeSessionId } = msg;
|
|
1077
|
-
handleBtwQuestion(question, conversationId,
|
|
942
|
+
handleBtwQuestion(question, conversationId, state.workDir, send, claudeSessionId);
|
|
1078
943
|
break;
|
|
1079
944
|
}
|
|
1080
945
|
case 'set_model': {
|
|
@@ -1098,7 +963,7 @@ function handleServerMessage(msg) {
|
|
|
1098
963
|
const { enabled, conversationId } = msg;
|
|
1099
964
|
console.log(`[AgentLink] set_plan_mode: enabled=${enabled}, conversationId=${conversationId}`);
|
|
1100
965
|
const convId = conversationId ?? 'default';
|
|
1101
|
-
const result = runtime?.setPlanMode(convId, enabled,
|
|
966
|
+
const result = runtime?.setPlanMode(convId, enabled, state.workDir) ?? null;
|
|
1102
967
|
if (result?.wasTurnActive) {
|
|
1103
968
|
send({ type: 'execution_cancelled', conversationId });
|
|
1104
969
|
}
|
|
@@ -1109,7 +974,7 @@ function handleServerMessage(msg) {
|
|
|
1109
974
|
const { mode, conversationId } = msg;
|
|
1110
975
|
console.log(`[AgentLink] set_permission_mode: mode=${mode}, conversationId=${conversationId}`);
|
|
1111
976
|
const convId = conversationId ?? 'default';
|
|
1112
|
-
const result = runtime?.setPermissionMode(convId, mode,
|
|
977
|
+
const result = runtime?.setPermissionMode(convId, mode, state.workDir) ?? null;
|
|
1113
978
|
if (result?.wasTurnActive) {
|
|
1114
979
|
send({ type: 'execution_cancelled', conversationId });
|
|
1115
980
|
}
|
|
@@ -1139,68 +1004,46 @@ function handleServerMessage(msg) {
|
|
|
1139
1004
|
const { enabled, conversationId } = msg;
|
|
1140
1005
|
console.log(`[AgentLink] set_brain_mode: enabled=${enabled}, conversationId=${conversationId}`);
|
|
1141
1006
|
const convId = conversationId ?? 'default';
|
|
1142
|
-
const result = runtime?.setBrainMode(convId, enabled,
|
|
1007
|
+
const result = runtime?.setBrainMode(convId, enabled, state.workDir) ?? null;
|
|
1143
1008
|
if (result?.wasTurnActive) {
|
|
1144
1009
|
send({ type: 'execution_cancelled', conversationId });
|
|
1145
1010
|
}
|
|
1146
1011
|
send({ type: 'brain_mode_changed', enabled, conversationId });
|
|
1147
1012
|
break;
|
|
1148
1013
|
}
|
|
1149
|
-
case 'git_worktree_list':
|
|
1150
|
-
|
|
1151
|
-
handleGitWorktreeList(msg, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1014
|
+
case 'git_worktree_list':
|
|
1015
|
+
handleGitWorktreeList(msg, state.workDir, send);
|
|
1152
1016
|
break;
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
const m = msg;
|
|
1156
|
-
handleGitStatus(msg, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1017
|
+
case 'git_status':
|
|
1018
|
+
handleGitStatus(msg, state.workDir, send);
|
|
1157
1019
|
break;
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const m = msg;
|
|
1161
|
-
handleGitDiff(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1020
|
+
case 'git_diff':
|
|
1021
|
+
handleGitDiff(msg, state.workDir, send);
|
|
1162
1022
|
break;
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
const m = msg;
|
|
1166
|
-
handleGitStage(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1023
|
+
case 'git_stage':
|
|
1024
|
+
handleGitStage(msg, state.workDir, send);
|
|
1167
1025
|
break;
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
const m = msg;
|
|
1171
|
-
handleGitUnstage(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1026
|
+
case 'git_unstage':
|
|
1027
|
+
handleGitUnstage(msg, state.workDir, send);
|
|
1172
1028
|
break;
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const m = msg;
|
|
1176
|
-
handleGitDiscard(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1029
|
+
case 'git_discard':
|
|
1030
|
+
handleGitDiscard(msg, state.workDir, send);
|
|
1177
1031
|
break;
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
const m = msg;
|
|
1181
|
-
handleGitCommit(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1032
|
+
case 'git_commit':
|
|
1033
|
+
handleGitCommit(msg, state.workDir, send);
|
|
1182
1034
|
break;
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const m = msg;
|
|
1186
|
-
handleGitStash(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1035
|
+
case 'git_stash':
|
|
1036
|
+
handleGitStash(msg, state.workDir, send);
|
|
1187
1037
|
break;
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const m = msg;
|
|
1191
|
-
handleGitPull(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1038
|
+
case 'git_pull':
|
|
1039
|
+
handleGitPull(msg, state.workDir, send);
|
|
1192
1040
|
break;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
const m = msg;
|
|
1196
|
-
handleGitPush(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1041
|
+
case 'git_push':
|
|
1042
|
+
handleGitPush(msg, state.workDir, send);
|
|
1197
1043
|
break;
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
const m = msg;
|
|
1201
|
-
handleGitDeleteFile(m, resolveWorkDir(msg, state, runtime), tag(send, m.conversationId));
|
|
1044
|
+
case 'git_delete_file':
|
|
1045
|
+
handleGitDeleteFile(msg, state.workDir, send);
|
|
1202
1046
|
break;
|
|
1203
|
-
}
|
|
1204
1047
|
case 'list_recaps':
|
|
1205
1048
|
handleListRecaps();
|
|
1206
1049
|
break;
|
|
@@ -1296,7 +1139,7 @@ function handleServerMessage(msg) {
|
|
|
1296
1139
|
// ── Terminal handlers ──────────────────────────────────────────────
|
|
1297
1140
|
case 'terminal_open':
|
|
1298
1141
|
if (terminalManager)
|
|
1299
|
-
terminalManager.open(msg.cols, msg.rows,
|
|
1142
|
+
terminalManager.open(msg.cols, msg.rows, state.workDir);
|
|
1300
1143
|
break;
|
|
1301
1144
|
case 'terminal_input':
|
|
1302
1145
|
if (terminalManager)
|
|
@@ -1561,30 +1404,21 @@ async function handleBrainChatDeleteSession(msg) {
|
|
|
1561
1404
|
send({ type: 'brainchat_error', message: String(err) });
|
|
1562
1405
|
}
|
|
1563
1406
|
}
|
|
1564
|
-
async function handleListSessions(
|
|
1565
|
-
const reply = (m) => {
|
|
1566
|
-
if (conversationId)
|
|
1567
|
-
m.conversationId = conversationId;
|
|
1568
|
-
send(m);
|
|
1569
|
-
};
|
|
1407
|
+
async function handleListSessions() {
|
|
1570
1408
|
try {
|
|
1571
1409
|
if (!runtime) {
|
|
1572
|
-
|
|
1410
|
+
send({ type: 'sessions_list', sessions: [], workDir: state.workDir });
|
|
1573
1411
|
return;
|
|
1574
1412
|
}
|
|
1575
1413
|
// PR4-D: runtime.listHistory returns BackendSessionInfo rows; map to the
|
|
1576
1414
|
// flat wire shape connection.ts has always sent ({sessionId, title, ...}).
|
|
1577
|
-
const rows = await runtime.listHistory(
|
|
1415
|
+
const rows = await runtime.listHistory(state.workDir);
|
|
1578
1416
|
const sessions = rows.map(r => ({
|
|
1579
1417
|
sessionId: r.session.backendSessionId,
|
|
1580
1418
|
title: r.title,
|
|
1581
1419
|
customTitle: r.customTitle,
|
|
1582
1420
|
preview: r.preview,
|
|
1583
1421
|
lastModified: r.lastModified,
|
|
1584
|
-
// PROJECT PATH: tag each session with the workdir its JSONL lives under,
|
|
1585
|
-
// so the web client can `set_conversation_workdir` before resuming
|
|
1586
|
-
// (otherwise per-conv resume reads the wrong directory). See sidebar.js.
|
|
1587
|
-
projectPath: resolvedWorkDir,
|
|
1588
1422
|
}));
|
|
1589
1423
|
const metaMap = loadAllSessionMetadata();
|
|
1590
1424
|
const enriched = sessions.map(s => ({
|
|
@@ -1592,7 +1426,7 @@ async function handleListSessions(resolvedWorkDir, conversationId) {
|
|
|
1592
1426
|
...metaMap.get(s.sessionId),
|
|
1593
1427
|
}));
|
|
1594
1428
|
// Always merge recap/briefing/devops chat sessions from BrainData directory — they live under a
|
|
1595
|
-
// different Claude project folder, so listHistory(
|
|
1429
|
+
// different Claude project folder, so listHistory(state.workDir) misses them.
|
|
1596
1430
|
// These sessions should be visible regardless of the current workDir.
|
|
1597
1431
|
const brainRows = await runtime.listHistory(BRAIN_DATA_DIR);
|
|
1598
1432
|
const brainSessions = brainRows.map(r => ({
|
|
@@ -1601,7 +1435,6 @@ async function handleListSessions(resolvedWorkDir, conversationId) {
|
|
|
1601
1435
|
customTitle: r.customTitle,
|
|
1602
1436
|
preview: r.preview,
|
|
1603
1437
|
lastModified: r.lastModified,
|
|
1604
|
-
projectPath: BRAIN_DATA_DIR,
|
|
1605
1438
|
}));
|
|
1606
1439
|
const existingIds = new Set(enriched.map(s => s.sessionId));
|
|
1607
1440
|
for (const bs of brainSessions) {
|
|
@@ -1613,12 +1446,12 @@ async function handleListSessions(resolvedWorkDir, conversationId) {
|
|
|
1613
1446
|
}
|
|
1614
1447
|
}
|
|
1615
1448
|
const cs = getSessionCacheStats();
|
|
1616
|
-
console.log(`[AgentLink] → sessions_list (${enriched.length} sessions for ${
|
|
1617
|
-
|
|
1449
|
+
console.log(`[AgentLink] → sessions_list (${enriched.length} sessions for ${state.workDir}) cache size=${cs.size} hits=${cs.hits} misses=${cs.misses} stale=${cs.staleMisses} ttlSkips=${cs.ttlSkips} evictions=${cs.evictions}`);
|
|
1450
|
+
send({ type: 'sessions_list', sessions: enriched, workDir: state.workDir });
|
|
1618
1451
|
}
|
|
1619
1452
|
catch (err) {
|
|
1620
1453
|
console.error(`[AgentLink] listSessions failed:`, err);
|
|
1621
|
-
|
|
1454
|
+
send({ type: 'sessions_list', sessions: [], workDir: state.workDir });
|
|
1622
1455
|
}
|
|
1623
1456
|
}
|
|
1624
1457
|
async function handleListRecentSessions(msg) {
|
|
@@ -1639,11 +1472,9 @@ async function handleListRecentSessions(msg) {
|
|
|
1639
1472
|
// echoing the searchId.
|
|
1640
1473
|
const searchControllers = new Map();
|
|
1641
1474
|
function handleSearchSessions(msg) {
|
|
1642
|
-
const { searchId, query, limit
|
|
1475
|
+
const { searchId, query, limit } = msg;
|
|
1643
1476
|
if (!searchId)
|
|
1644
1477
|
return;
|
|
1645
|
-
const tagged = tag(send, conversationId);
|
|
1646
|
-
const resolvedWorkDir = resolveWorkDir(msg, state, runtime);
|
|
1647
1478
|
// Abort any prior in-flight search (only one at a time).
|
|
1648
1479
|
for (const [id, ctrl] of searchControllers) {
|
|
1649
1480
|
ctrl.abort();
|
|
@@ -1659,11 +1490,11 @@ function handleSearchSessions(msg) {
|
|
|
1659
1490
|
return;
|
|
1660
1491
|
}
|
|
1661
1492
|
try {
|
|
1662
|
-
const result = searchSessions(
|
|
1493
|
+
const result = searchSessions(state.workDir, query, { limit, signal: controller.signal });
|
|
1663
1494
|
if (controller.signal.aborted)
|
|
1664
1495
|
return;
|
|
1665
1496
|
console.log(`[AgentLink] → session_search_results (q="${query}" hits=${result.results.reduce((s, r) => s + r.hits.length, 0)} scanned=${result.scannedFiles} matched=${result.matchedFiles} ${result.durationMs}ms)`);
|
|
1666
|
-
|
|
1497
|
+
send({
|
|
1667
1498
|
type: 'session_search_results',
|
|
1668
1499
|
searchId,
|
|
1669
1500
|
...result,
|
|
@@ -1671,7 +1502,7 @@ function handleSearchSessions(msg) {
|
|
|
1671
1502
|
}
|
|
1672
1503
|
catch (err) {
|
|
1673
1504
|
console.error('[AgentLink] searchSessions failed:', err);
|
|
1674
|
-
|
|
1505
|
+
send({
|
|
1675
1506
|
type: 'session_search_error',
|
|
1676
1507
|
searchId,
|
|
1677
1508
|
message: String(err),
|
|
@@ -1689,19 +1520,18 @@ function handleCancelSearch(msg) {
|
|
|
1689
1520
|
searchControllers.delete(msg.searchId);
|
|
1690
1521
|
}
|
|
1691
1522
|
}
|
|
1692
|
-
async function handleDeleteSession(sessionId
|
|
1693
|
-
const reply = tag(send, conversationId);
|
|
1523
|
+
async function handleDeleteSession(sessionId) {
|
|
1694
1524
|
// Evict any idle conversation holding this session; block if busy
|
|
1695
1525
|
if (evictByClaudeSessionId(sessionId)) {
|
|
1696
|
-
|
|
1526
|
+
send({ type: 'error', message: 'Cannot delete a session while it is processing.' });
|
|
1697
1527
|
return;
|
|
1698
1528
|
}
|
|
1699
1529
|
if (!runtime) {
|
|
1700
|
-
|
|
1530
|
+
send({ type: 'error', message: 'Session not found or could not be deleted.' });
|
|
1701
1531
|
return;
|
|
1702
1532
|
}
|
|
1703
1533
|
// Try current workDir first; if not found, check if it's a recap/briefing/devops/project session in BrainData
|
|
1704
|
-
let deleted = await runtime.deleteHistorySession(
|
|
1534
|
+
let deleted = await runtime.deleteHistorySession(state.workDir, sessionId);
|
|
1705
1535
|
if (!deleted) {
|
|
1706
1536
|
const meta = loadSessionMetadata(sessionId);
|
|
1707
1537
|
if (meta.recapId || meta.briefingDate || meta.devopsEntityType || meta.projectName || meta.icmId) {
|
|
@@ -1710,20 +1540,19 @@ async function handleDeleteSession(sessionId, resolvedWorkDir, conversationId) {
|
|
|
1710
1540
|
}
|
|
1711
1541
|
if (deleted) {
|
|
1712
1542
|
deleteSessionMetadata(sessionId);
|
|
1713
|
-
|
|
1543
|
+
send({ type: 'session_deleted', sessionId });
|
|
1714
1544
|
}
|
|
1715
1545
|
else {
|
|
1716
|
-
|
|
1546
|
+
send({ type: 'error', message: 'Session not found or could not be deleted.' });
|
|
1717
1547
|
}
|
|
1718
1548
|
}
|
|
1719
|
-
async function handleRenameSession(sessionId, newTitle
|
|
1720
|
-
const reply = tag(send, conversationId);
|
|
1549
|
+
async function handleRenameSession(sessionId, newTitle) {
|
|
1721
1550
|
if (!runtime) {
|
|
1722
|
-
|
|
1551
|
+
send({ type: 'error', message: 'Session not found or could not be renamed.' });
|
|
1723
1552
|
return;
|
|
1724
1553
|
}
|
|
1725
1554
|
// Try current workDir first; if not found, check if it's a recap/briefing/devops/project session in BrainData
|
|
1726
|
-
let renamed = await runtime.renameHistorySession(
|
|
1555
|
+
let renamed = await runtime.renameHistorySession(state.workDir, sessionId, newTitle);
|
|
1727
1556
|
if (!renamed) {
|
|
1728
1557
|
const meta = loadSessionMetadata(sessionId);
|
|
1729
1558
|
if (meta.recapId || meta.briefingDate || meta.devopsEntityType || meta.projectName || meta.icmId) {
|
|
@@ -1731,25 +1560,25 @@ async function handleRenameSession(sessionId, newTitle, resolvedWorkDir, convers
|
|
|
1731
1560
|
}
|
|
1732
1561
|
}
|
|
1733
1562
|
if (renamed) {
|
|
1734
|
-
|
|
1563
|
+
send({ type: 'session_renamed', sessionId, newTitle });
|
|
1735
1564
|
}
|
|
1736
1565
|
else {
|
|
1737
1566
|
// Session file may not exist on disk yet (e.g. IcM/project chat still streaming first response).
|
|
1738
1567
|
// Retry once after a short delay; if it still fails, send an error so the UI can recover.
|
|
1739
1568
|
setTimeout(() => {
|
|
1740
1569
|
(async () => {
|
|
1741
|
-
const retried = (await runtime.renameHistorySession(
|
|
1570
|
+
const retried = (await runtime.renameHistorySession(state.workDir, sessionId, newTitle))
|
|
1742
1571
|
|| (await runtime.renameHistorySession(BRAIN_DATA_DIR, sessionId, newTitle));
|
|
1743
1572
|
if (retried) {
|
|
1744
|
-
|
|
1573
|
+
send({ type: 'session_renamed', sessionId, newTitle });
|
|
1745
1574
|
}
|
|
1746
1575
|
else {
|
|
1747
1576
|
console.warn(`[AgentLink] rename_session: session ${sessionId.slice(0, 8)} not found after retry`);
|
|
1748
|
-
|
|
1577
|
+
send({ type: 'error', message: 'Session not found or could not be renamed.' });
|
|
1749
1578
|
}
|
|
1750
1579
|
})().catch((err) => {
|
|
1751
1580
|
console.error('[AgentLink] rename_session retry failed:', err);
|
|
1752
|
-
|
|
1581
|
+
send({ type: 'error', message: 'Session not found or could not be renamed.' });
|
|
1753
1582
|
});
|
|
1754
1583
|
}, 1500);
|
|
1755
1584
|
}
|