opencode-gitlab-duo-agentic 0.1.24 → 0.2.2
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/index.js +305 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ var WORKFLOW_CONNECT_TIMEOUT_MS = 15e3;
|
|
|
10
10
|
var WORKFLOW_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
11
11
|
var WORKFLOW_KEEPALIVE_INTERVAL_MS = 45e3;
|
|
12
12
|
var WORKFLOW_TOKEN_EXPIRY_BUFFER_MS = 3e4;
|
|
13
|
-
var WORKFLOW_TOOL_ERROR_MESSAGE = "Tool execution is not implemented in this client yet";
|
|
14
13
|
|
|
15
14
|
// src/gitlab/models.ts
|
|
16
15
|
import crypto from "crypto";
|
|
@@ -136,8 +135,8 @@ async function resolveRootNamespaceId(client, namespaceId) {
|
|
|
136
135
|
}
|
|
137
136
|
async function readGitConfig(cwd) {
|
|
138
137
|
const gitPath = path.join(cwd, ".git");
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
138
|
+
const stat2 = await fs.stat(gitPath);
|
|
139
|
+
if (stat2.isDirectory()) {
|
|
141
140
|
return fs.readFile(path.join(gitPath, "config"), "utf8");
|
|
142
141
|
}
|
|
143
142
|
const content = await fs.readFile(gitPath, "utf8");
|
|
@@ -412,7 +411,7 @@ var AsyncQueue = class {
|
|
|
412
411
|
shift() {
|
|
413
412
|
const value = this.#values.shift();
|
|
414
413
|
if (value !== void 0) return Promise.resolve(value);
|
|
415
|
-
return new Promise((
|
|
414
|
+
return new Promise((resolve2) => this.#waiters.push(resolve2));
|
|
416
415
|
}
|
|
417
416
|
};
|
|
418
417
|
|
|
@@ -543,7 +542,7 @@ var WorkflowWebSocketClient = class {
|
|
|
543
542
|
async connect(url, headers) {
|
|
544
543
|
const socket = new WebSocket(url, { headers });
|
|
545
544
|
this.#socket = socket;
|
|
546
|
-
await new Promise((
|
|
545
|
+
await new Promise((resolve2, reject) => {
|
|
547
546
|
const timeout = setTimeout(() => {
|
|
548
547
|
cleanup();
|
|
549
548
|
socket.close(1e3);
|
|
@@ -556,7 +555,7 @@ var WorkflowWebSocketClient = class {
|
|
|
556
555
|
};
|
|
557
556
|
const onOpen = () => {
|
|
558
557
|
cleanup();
|
|
559
|
-
|
|
558
|
+
resolve2();
|
|
560
559
|
};
|
|
561
560
|
const onError = (error) => {
|
|
562
561
|
cleanup();
|
|
@@ -621,6 +620,268 @@ function decodeSocketMessage(data) {
|
|
|
621
620
|
return void 0;
|
|
622
621
|
}
|
|
623
622
|
|
|
623
|
+
// src/workflow/tool-executor.ts
|
|
624
|
+
import { readFile, readdir, writeFile, mkdir as fsMkdir, stat } from "fs/promises";
|
|
625
|
+
import { exec } from "child_process";
|
|
626
|
+
import { resolve, dirname } from "path";
|
|
627
|
+
var ToolExecutor = class {
|
|
628
|
+
#cwd;
|
|
629
|
+
#client;
|
|
630
|
+
constructor(cwd, client) {
|
|
631
|
+
this.#cwd = cwd;
|
|
632
|
+
this.#client = client;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Route a Duo WorkflowToolAction to the appropriate local handler.
|
|
636
|
+
* This is the main bridge entry point.
|
|
637
|
+
*/
|
|
638
|
+
async executeAction(action) {
|
|
639
|
+
try {
|
|
640
|
+
if (action.runReadFile) {
|
|
641
|
+
return this.#read({
|
|
642
|
+
filePath: action.runReadFile.filepath,
|
|
643
|
+
offset: action.runReadFile.offset,
|
|
644
|
+
limit: action.runReadFile.limit
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (action.runReadFiles) {
|
|
648
|
+
return this.#readMultiple(action.runReadFiles.filepaths);
|
|
649
|
+
}
|
|
650
|
+
if (action.runWriteFile) {
|
|
651
|
+
return this.#write({
|
|
652
|
+
filePath: action.runWriteFile.filepath,
|
|
653
|
+
content: action.runWriteFile.contents
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
if (action.runEditFile) {
|
|
657
|
+
return this.#edit({
|
|
658
|
+
filePath: action.runEditFile.filepath,
|
|
659
|
+
oldString: action.runEditFile.oldString,
|
|
660
|
+
newString: action.runEditFile.newString
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
if (action.runShellCommand) {
|
|
664
|
+
return this.#bash({ command: action.runShellCommand.command });
|
|
665
|
+
}
|
|
666
|
+
if (action.runCommand) {
|
|
667
|
+
const parts = [action.runCommand.program];
|
|
668
|
+
if (action.runCommand.flags) parts.push(...action.runCommand.flags);
|
|
669
|
+
if (action.runCommand.arguments) parts.push(...action.runCommand.arguments);
|
|
670
|
+
return this.#bash({ command: parts.join(" ") });
|
|
671
|
+
}
|
|
672
|
+
if (action.runGitCommand) {
|
|
673
|
+
const cmd = action.runGitCommand.arguments ? `git ${action.runGitCommand.command} ${action.runGitCommand.arguments}` : `git ${action.runGitCommand.command}`;
|
|
674
|
+
return this.#bash({ command: cmd });
|
|
675
|
+
}
|
|
676
|
+
if (action.listDirectory) {
|
|
677
|
+
return this.#read({ filePath: action.listDirectory.directory });
|
|
678
|
+
}
|
|
679
|
+
if (action.grep) {
|
|
680
|
+
return this.#grep({
|
|
681
|
+
pattern: action.grep.pattern,
|
|
682
|
+
path: action.grep.search_directory,
|
|
683
|
+
caseInsensitive: action.grep.case_insensitive
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (action.findFiles) {
|
|
687
|
+
return this.#glob({ pattern: action.findFiles.name_pattern });
|
|
688
|
+
}
|
|
689
|
+
if (action.mkdir) {
|
|
690
|
+
return this.#mkdir(action.mkdir.directory_path);
|
|
691
|
+
}
|
|
692
|
+
if (action.runMCPTool) {
|
|
693
|
+
return this.#executeMcpTool(action.runMCPTool.name, action.runMCPTool.args);
|
|
694
|
+
}
|
|
695
|
+
if (action.runHTTPRequest) {
|
|
696
|
+
return this.#httpRequest(action.runHTTPRequest);
|
|
697
|
+
}
|
|
698
|
+
return { response: "", error: "Unknown tool action" };
|
|
699
|
+
} catch (err) {
|
|
700
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Execute an MCP tool by name (for runMCPTool actions).
|
|
705
|
+
* Routes to the appropriate handler based on tool name.
|
|
706
|
+
*/
|
|
707
|
+
async #executeMcpTool(name, argsJson) {
|
|
708
|
+
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
709
|
+
switch (name) {
|
|
710
|
+
case "bash":
|
|
711
|
+
return this.#bash(args);
|
|
712
|
+
case "read":
|
|
713
|
+
return this.#read(args);
|
|
714
|
+
case "edit":
|
|
715
|
+
return this.#edit(args);
|
|
716
|
+
case "write":
|
|
717
|
+
return this.#write(args);
|
|
718
|
+
case "glob":
|
|
719
|
+
return this.#glob(args);
|
|
720
|
+
case "grep":
|
|
721
|
+
return this.#grep(args);
|
|
722
|
+
default:
|
|
723
|
+
return { response: "", error: `Unknown MCP tool: ${name}` };
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// ── Handlers ──────────────────────────────────────────────────────
|
|
727
|
+
async #bash(args) {
|
|
728
|
+
const cwd = args.workdir ? resolve(this.#cwd, args.workdir) : this.#cwd;
|
|
729
|
+
const timeout = args.timeout ?? 12e4;
|
|
730
|
+
return new Promise((res) => {
|
|
731
|
+
exec(args.command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
732
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
733
|
+
if (error) {
|
|
734
|
+
res({ response: output, error: error.message });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
res({ response: output });
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
async #read(args) {
|
|
742
|
+
try {
|
|
743
|
+
const filepath = resolve(this.#cwd, args.filePath);
|
|
744
|
+
const info = await stat(filepath);
|
|
745
|
+
if (info.isDirectory()) {
|
|
746
|
+
const entries = await readdir(filepath, { withFileTypes: true });
|
|
747
|
+
const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
748
|
+
return { response: lines.join("\n") };
|
|
749
|
+
}
|
|
750
|
+
const content = await readFile(filepath, "utf-8");
|
|
751
|
+
const allLines = content.split("\n");
|
|
752
|
+
const offset = Math.max((args.offset ?? 1) - 1, 0);
|
|
753
|
+
const limit = args.limit ?? 2e3;
|
|
754
|
+
const slice = allLines.slice(offset, offset + limit);
|
|
755
|
+
const numbered = slice.map((line, i) => `${offset + i + 1}: ${line}`);
|
|
756
|
+
return { response: numbered.join("\n") };
|
|
757
|
+
} catch (err) {
|
|
758
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
async #readMultiple(filepaths) {
|
|
762
|
+
const results = [];
|
|
763
|
+
for (const fp of filepaths) {
|
|
764
|
+
const result = await this.#read({ filePath: fp });
|
|
765
|
+
if (result.error) {
|
|
766
|
+
results.push(`--- ${fp} ---
|
|
767
|
+
ERROR: ${result.error}`);
|
|
768
|
+
} else {
|
|
769
|
+
results.push(`--- ${fp} ---
|
|
770
|
+
${result.response}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return { response: results.join("\n\n") };
|
|
774
|
+
}
|
|
775
|
+
async #edit(args) {
|
|
776
|
+
try {
|
|
777
|
+
const filepath = resolve(this.#cwd, args.filePath);
|
|
778
|
+
let content = await readFile(filepath, "utf-8");
|
|
779
|
+
if (args.replaceAll) {
|
|
780
|
+
if (!content.includes(args.oldString)) {
|
|
781
|
+
return { response: "", error: "oldString not found in content" };
|
|
782
|
+
}
|
|
783
|
+
content = content.replaceAll(args.oldString, args.newString);
|
|
784
|
+
} else {
|
|
785
|
+
const idx = content.indexOf(args.oldString);
|
|
786
|
+
if (idx === -1) {
|
|
787
|
+
return { response: "", error: "oldString not found in content" };
|
|
788
|
+
}
|
|
789
|
+
const secondIdx = content.indexOf(args.oldString, idx + 1);
|
|
790
|
+
if (secondIdx !== -1) {
|
|
791
|
+
return { response: "", error: "Found multiple matches for oldString. Provide more surrounding lines to identify the correct match." };
|
|
792
|
+
}
|
|
793
|
+
content = content.slice(0, idx) + args.newString + content.slice(idx + args.oldString.length);
|
|
794
|
+
}
|
|
795
|
+
await writeFile(filepath, content, "utf-8");
|
|
796
|
+
return { response: `Successfully edited ${args.filePath}` };
|
|
797
|
+
} catch (err) {
|
|
798
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async #write(args) {
|
|
802
|
+
try {
|
|
803
|
+
const filepath = resolve(this.#cwd, args.filePath);
|
|
804
|
+
await fsMkdir(dirname(filepath), { recursive: true });
|
|
805
|
+
await writeFile(filepath, args.content, "utf-8");
|
|
806
|
+
return { response: `Successfully wrote ${args.filePath}` };
|
|
807
|
+
} catch (err) {
|
|
808
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async #glob(args) {
|
|
812
|
+
const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
|
|
813
|
+
return new Promise((res) => {
|
|
814
|
+
const command = process.platform === "win32" ? `dir /s /b "${args.pattern}"` : `find . -path "./${args.pattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -200`;
|
|
815
|
+
exec(command, { cwd, timeout: 3e4 }, (error, stdout) => {
|
|
816
|
+
if (error && !stdout) {
|
|
817
|
+
exec(`ls -1 ${args.pattern} 2>/dev/null | head -200`, { cwd, timeout: 3e4 }, (_err, out) => {
|
|
818
|
+
res({ response: out.trim() || "No matches found" });
|
|
819
|
+
});
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
823
|
+
res({ response: lines.length > 0 ? lines.join("\n") : "No matches found" });
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
async #grep(args) {
|
|
828
|
+
const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
|
|
829
|
+
const caseFlag = args.caseInsensitive ? "--ignore-case" : "";
|
|
830
|
+
return new Promise((res) => {
|
|
831
|
+
const includeFlag = args.include ? `--glob '${args.include}'` : "";
|
|
832
|
+
const escapedPattern = args.pattern.replace(/'/g, "'\\''");
|
|
833
|
+
const rgCommand = `rg --line-number --no-heading ${caseFlag} ${includeFlag} '${escapedPattern}' . 2>/dev/null | head -500`;
|
|
834
|
+
exec(rgCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_error, stdout) => {
|
|
835
|
+
if (stdout.trim()) {
|
|
836
|
+
res({ response: stdout.trim() });
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const grepCaseFlag = args.caseInsensitive ? "-i" : "";
|
|
840
|
+
const grepInclude = args.include ? `--include='${args.include}'` : "";
|
|
841
|
+
const grepCommand = `grep -rn ${grepCaseFlag} ${grepInclude} '${escapedPattern}' . 2>/dev/null | head -500`;
|
|
842
|
+
exec(grepCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_err, out) => {
|
|
843
|
+
res({ response: out.trim() || "No matches found" });
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
async #mkdir(directoryPath) {
|
|
849
|
+
try {
|
|
850
|
+
const filepath = resolve(this.#cwd, directoryPath);
|
|
851
|
+
await fsMkdir(filepath, { recursive: true });
|
|
852
|
+
return { response: `Created directory ${directoryPath}` };
|
|
853
|
+
} catch (err) {
|
|
854
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async #httpRequest(args) {
|
|
858
|
+
if (!this.#client) {
|
|
859
|
+
return { response: "", error: "HTTP requests require GitLab client credentials" };
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const url = `${this.#client.instanceUrl}/api/v4/${args.path}`;
|
|
863
|
+
const headers = {
|
|
864
|
+
authorization: `Bearer ${this.#client.token}`
|
|
865
|
+
};
|
|
866
|
+
if (args.body) {
|
|
867
|
+
headers["content-type"] = "application/json";
|
|
868
|
+
}
|
|
869
|
+
const response = await fetch(url, {
|
|
870
|
+
method: args.method,
|
|
871
|
+
headers,
|
|
872
|
+
body: args.body ?? void 0
|
|
873
|
+
});
|
|
874
|
+
const text2 = await response.text();
|
|
875
|
+
if (!response.ok) {
|
|
876
|
+
return { response: text2, error: `HTTP ${response.status}: ${response.statusText}` };
|
|
877
|
+
}
|
|
878
|
+
return { response: text2 };
|
|
879
|
+
} catch (err) {
|
|
880
|
+
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
624
885
|
// src/workflow/session.ts
|
|
625
886
|
var WorkflowSession = class {
|
|
626
887
|
#client;
|
|
@@ -631,6 +892,8 @@ var WorkflowSession = class {
|
|
|
631
892
|
#projectPath;
|
|
632
893
|
#rootNamespaceId;
|
|
633
894
|
#checkpoint = createCheckpointState();
|
|
895
|
+
#toolsConfig;
|
|
896
|
+
#toolExecutor;
|
|
634
897
|
/** Mutex: serialises concurrent calls to runTurn so only one runs at a time. */
|
|
635
898
|
#turnLock = Promise.resolve();
|
|
636
899
|
constructor(client, modelId, cwd) {
|
|
@@ -638,6 +901,14 @@ var WorkflowSession = class {
|
|
|
638
901
|
this.#tokenService = new WorkflowTokenService(client);
|
|
639
902
|
this.#modelId = modelId;
|
|
640
903
|
this.#cwd = cwd;
|
|
904
|
+
this.#toolExecutor = new ToolExecutor(cwd, client);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
908
|
+
* When not called, the server uses its default prompt and built-in tools.
|
|
909
|
+
*/
|
|
910
|
+
setToolsConfig(config) {
|
|
911
|
+
this.#toolsConfig = config;
|
|
641
912
|
}
|
|
642
913
|
get workflowId() {
|
|
643
914
|
return this.#workflowId;
|
|
@@ -649,9 +920,9 @@ var WorkflowSession = class {
|
|
|
649
920
|
}
|
|
650
921
|
async *runTurn(goal, abortSignal) {
|
|
651
922
|
await this.#turnLock;
|
|
652
|
-
let
|
|
923
|
+
let resolve2;
|
|
653
924
|
this.#turnLock = new Promise((r) => {
|
|
654
|
-
|
|
925
|
+
resolve2 = r;
|
|
655
926
|
});
|
|
656
927
|
const queue = new AsyncQueue();
|
|
657
928
|
const socket = new WorkflowWebSocketClient({
|
|
@@ -679,6 +950,8 @@ var WorkflowSession = class {
|
|
|
679
950
|
"x-gitlab-client-type": "node-websocket"
|
|
680
951
|
});
|
|
681
952
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
953
|
+
const mcpTools = this.#toolsConfig?.mcpTools ?? [];
|
|
954
|
+
const preapprovedTools = mcpTools.map((t) => t.name);
|
|
682
955
|
const sent = socket.send({
|
|
683
956
|
startRequest: {
|
|
684
957
|
workflowID: this.#workflowId,
|
|
@@ -689,9 +962,14 @@ var WorkflowSession = class {
|
|
|
689
962
|
extended_logging: access?.workflow_metadata?.extended_logging ?? false
|
|
690
963
|
}),
|
|
691
964
|
clientCapabilities: [],
|
|
692
|
-
mcpTools
|
|
965
|
+
mcpTools,
|
|
693
966
|
additional_context: [],
|
|
694
|
-
preapproved_tools:
|
|
967
|
+
preapproved_tools: preapprovedTools,
|
|
968
|
+
// Only include flowConfig when explicitly configured (opt-in prompt override)
|
|
969
|
+
...this.#toolsConfig?.flowConfig ? {
|
|
970
|
+
flowConfig: this.#toolsConfig.flowConfig,
|
|
971
|
+
flowConfigSchemaVersion: this.#toolsConfig.flowConfigSchemaVersion ?? "v1"
|
|
972
|
+
} : {}
|
|
695
973
|
}
|
|
696
974
|
});
|
|
697
975
|
if (!sent) throw new Error("failed to send workflow startRequest");
|
|
@@ -716,12 +994,13 @@ var WorkflowSession = class {
|
|
|
716
994
|
continue;
|
|
717
995
|
}
|
|
718
996
|
if (!event.action.requestID) continue;
|
|
997
|
+
const result = await this.#toolExecutor.executeAction(event.action);
|
|
719
998
|
socket.send({
|
|
720
999
|
actionResponse: {
|
|
721
1000
|
requestID: event.action.requestID,
|
|
722
1001
|
plainTextResponse: {
|
|
723
|
-
response:
|
|
724
|
-
error:
|
|
1002
|
+
response: result.response,
|
|
1003
|
+
error: result.error ?? ""
|
|
725
1004
|
}
|
|
726
1005
|
}
|
|
727
1006
|
});
|
|
@@ -729,7 +1008,7 @@ var WorkflowSession = class {
|
|
|
729
1008
|
} finally {
|
|
730
1009
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
731
1010
|
socket.close();
|
|
732
|
-
|
|
1011
|
+
resolve2();
|
|
733
1012
|
}
|
|
734
1013
|
}
|
|
735
1014
|
async #createWorkflow(goal) {
|
|
@@ -826,11 +1105,23 @@ var DuoWorkflowModel = class {
|
|
|
826
1105
|
supportedUrls = {};
|
|
827
1106
|
#client;
|
|
828
1107
|
#cwd;
|
|
1108
|
+
#toolsConfig;
|
|
829
1109
|
constructor(modelId, client, cwd) {
|
|
830
1110
|
this.modelId = modelId;
|
|
831
1111
|
this.#client = client;
|
|
832
1112
|
this.#cwd = cwd ?? process.cwd();
|
|
833
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
1116
|
+
* When not called, the server uses its default prompt and built-in tools.
|
|
1117
|
+
* Tool execution is always bridged locally regardless of this setting.
|
|
1118
|
+
*/
|
|
1119
|
+
setToolsConfig(config) {
|
|
1120
|
+
this.#toolsConfig = config;
|
|
1121
|
+
for (const session of sessions.values()) {
|
|
1122
|
+
session.setToolsConfig(config);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
834
1125
|
async doGenerate(options) {
|
|
835
1126
|
const sessionID = readSessionID(options);
|
|
836
1127
|
if (!sessionID) throw new Error("missing workflow session ID");
|
|
@@ -928,6 +1219,7 @@ var DuoWorkflowModel = class {
|
|
|
928
1219
|
const existing = sessions.get(key);
|
|
929
1220
|
if (existing) return existing;
|
|
930
1221
|
const created = new WorkflowSession(this.#client, this.modelId, this.#cwd);
|
|
1222
|
+
if (this.#toolsConfig) created.setToolsConfig(this.#toolsConfig);
|
|
931
1223
|
sessions.set(key, created);
|
|
932
1224
|
return created;
|
|
933
1225
|
}
|