opencode-gitlab-duo-agentic 0.2.1 → 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 +161 -271
- 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";
|
|
@@ -343,236 +342,8 @@ function toModelsConfig(available) {
|
|
|
343
342
|
return out;
|
|
344
343
|
}
|
|
345
344
|
|
|
346
|
-
// src/workflow/opencode-tools.ts
|
|
347
|
-
var SUPPORTED_TOOLS = /* @__PURE__ */ new Set([
|
|
348
|
-
"bash",
|
|
349
|
-
"read",
|
|
350
|
-
"edit",
|
|
351
|
-
"write",
|
|
352
|
-
"glob",
|
|
353
|
-
"grep"
|
|
354
|
-
]);
|
|
355
|
-
async function fetchOpencodeTools(client, provider, model) {
|
|
356
|
-
try {
|
|
357
|
-
const response = await client.tool.list({ provider, model });
|
|
358
|
-
const tools = response.data ?? [];
|
|
359
|
-
return tools.filter((t) => SUPPORTED_TOOLS.has(t.id)).map((t) => ({
|
|
360
|
-
name: t.id,
|
|
361
|
-
description: t.description,
|
|
362
|
-
inputSchema: typeof t.parameters === "string" ? t.parameters : JSON.stringify(t.parameters)
|
|
363
|
-
}));
|
|
364
|
-
} catch {
|
|
365
|
-
return [];
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
function buildFallbackTools() {
|
|
369
|
-
return [
|
|
370
|
-
{
|
|
371
|
-
name: "bash",
|
|
372
|
-
description: "Execute a shell command. Use the `command` parameter for the command to run, `description` for a short summary, optional `timeout` in milliseconds, and optional `workdir` to set the working directory.",
|
|
373
|
-
inputSchema: JSON.stringify({
|
|
374
|
-
type: "object",
|
|
375
|
-
properties: {
|
|
376
|
-
command: { type: "string", description: "The command to execute" },
|
|
377
|
-
description: { type: "string", description: "Clear, concise description of what this command does in 5-10 words" },
|
|
378
|
-
timeout: { type: "number", description: "Optional timeout in milliseconds" },
|
|
379
|
-
workdir: { type: "string", description: "The working directory to run the command in" }
|
|
380
|
-
},
|
|
381
|
-
required: ["command", "description"]
|
|
382
|
-
})
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: "read",
|
|
386
|
-
description: "Read a file or directory from the local filesystem. Returns content with line numbers prefixed. Supports offset and limit for pagination. Can read images and PDFs as attachments.",
|
|
387
|
-
inputSchema: JSON.stringify({
|
|
388
|
-
type: "object",
|
|
389
|
-
properties: {
|
|
390
|
-
filePath: { type: "string", description: "The absolute path to the file or directory to read" },
|
|
391
|
-
offset: { type: "number", description: "The line number to start reading from (1-indexed)" },
|
|
392
|
-
limit: { type: "number", description: "The maximum number of lines to read (defaults to 2000)" }
|
|
393
|
-
},
|
|
394
|
-
required: ["filePath"]
|
|
395
|
-
})
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
name: "edit",
|
|
399
|
-
description: "Performs exact string replacements in files. The oldString must match the file contents exactly. Provide surrounding context to make the match unique.",
|
|
400
|
-
inputSchema: JSON.stringify({
|
|
401
|
-
type: "object",
|
|
402
|
-
properties: {
|
|
403
|
-
filePath: { type: "string", description: "The absolute path to the file to modify" },
|
|
404
|
-
oldString: { type: "string", description: "The text to replace" },
|
|
405
|
-
newString: { type: "string", description: "The text to replace it with" },
|
|
406
|
-
replaceAll: { type: "boolean", description: "Replace all occurrences (default false)" }
|
|
407
|
-
},
|
|
408
|
-
required: ["filePath", "oldString", "newString"]
|
|
409
|
-
})
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
name: "write",
|
|
413
|
-
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Parent directories are created automatically.",
|
|
414
|
-
inputSchema: JSON.stringify({
|
|
415
|
-
type: "object",
|
|
416
|
-
properties: {
|
|
417
|
-
filePath: { type: "string", description: "The absolute path to the file to write" },
|
|
418
|
-
content: { type: "string", description: "The content to write to the file" }
|
|
419
|
-
},
|
|
420
|
-
required: ["filePath", "content"]
|
|
421
|
-
})
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
name: "glob",
|
|
425
|
-
description: "Fast file pattern matching. Supports glob patterns like '**/*.js' or 'src/**/*.ts'. Returns matching file paths sorted by modification time.",
|
|
426
|
-
inputSchema: JSON.stringify({
|
|
427
|
-
type: "object",
|
|
428
|
-
properties: {
|
|
429
|
-
pattern: { type: "string", description: "The glob pattern to match files against" },
|
|
430
|
-
path: { type: "string", description: "The directory to search in" }
|
|
431
|
-
},
|
|
432
|
-
required: ["pattern"]
|
|
433
|
-
})
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: "grep",
|
|
437
|
-
description: "Fast content search using regular expressions. Returns file paths and line numbers with matches. Supports filtering files by pattern with the include parameter.",
|
|
438
|
-
inputSchema: JSON.stringify({
|
|
439
|
-
type: "object",
|
|
440
|
-
properties: {
|
|
441
|
-
pattern: { type: "string", description: "The regex pattern to search for in file contents" },
|
|
442
|
-
path: { type: "string", description: "The directory to search in" },
|
|
443
|
-
include: { type: "string", description: "File pattern to include in the search (e.g. '*.js', '*.{ts,tsx}')" }
|
|
444
|
-
},
|
|
445
|
-
required: ["pattern"]
|
|
446
|
-
})
|
|
447
|
-
}
|
|
448
|
-
];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// src/workflow/flow-config.ts
|
|
452
|
-
function buildFlowConfig(systemPrompt) {
|
|
453
|
-
return {
|
|
454
|
-
version: "v1",
|
|
455
|
-
environment: "chat-partial",
|
|
456
|
-
components: [
|
|
457
|
-
{
|
|
458
|
-
name: "opencode_agent",
|
|
459
|
-
type: "AgentComponent",
|
|
460
|
-
// Empty toolset: we rely entirely on MCP tools (OpenCode tools)
|
|
461
|
-
// registered via the mcpTools field in startRequest
|
|
462
|
-
toolset: []
|
|
463
|
-
}
|
|
464
|
-
],
|
|
465
|
-
prompts: [
|
|
466
|
-
{
|
|
467
|
-
name: "opencode_prompt",
|
|
468
|
-
prompt_id: "opencode_prompt",
|
|
469
|
-
model: {
|
|
470
|
-
params: {
|
|
471
|
-
model_class_provider: "anthropic",
|
|
472
|
-
max_tokens: 32768
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
unit_primitives: ["duo_chat"],
|
|
476
|
-
prompt_template: {
|
|
477
|
-
system: systemPrompt,
|
|
478
|
-
user: "{{goal}}",
|
|
479
|
-
placeholder: "history"
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
]
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
var OPENCODE_SYSTEM_PROMPT = `You are OpenCode, an AI coding assistant integrated with GitLab Duo.
|
|
486
|
-
|
|
487
|
-
<core_mission>
|
|
488
|
-
Your primary role is collaborative programming - working alongside the user to accomplish coding objectives using the tools available to you.
|
|
489
|
-
</core_mission>
|
|
490
|
-
|
|
491
|
-
<communication_guidelines>
|
|
492
|
-
- Provide clear and concise responses. Brevity and clarity are critical.
|
|
493
|
-
- Focus on clean, practical solutions that help users make progress.
|
|
494
|
-
- Keep responses brief and to the point. One-word answers are fine when they suffice.
|
|
495
|
-
- Use active, present-tense language: 'Analyzing...', 'Processing...' instead of 'Let me...', 'I will...'
|
|
496
|
-
- When unable to complete requests, explain the limitation concisely and provide alternatives.
|
|
497
|
-
- When users correct you, acknowledge briefly and apply the correction immediately.
|
|
498
|
-
</communication_guidelines>
|
|
499
|
-
|
|
500
|
-
<tool_usage>
|
|
501
|
-
You have access to file system and shell tools. Use them effectively:
|
|
502
|
-
|
|
503
|
-
- Use the 'read' tool to read files before editing them.
|
|
504
|
-
- Use the 'edit' tool for precise string replacements in existing files.
|
|
505
|
-
- Use the 'write' tool to create new files or overwrite existing ones.
|
|
506
|
-
- Use the 'glob' tool to find files by name patterns.
|
|
507
|
-
- Use the 'grep' tool to search file contents with regex.
|
|
508
|
-
- Use the 'bash' tool for shell commands (git, npm, docker, tests, builds, etc.).
|
|
509
|
-
|
|
510
|
-
Tool orchestration:
|
|
511
|
-
- Execute multiple independent tool operations in parallel when possible.
|
|
512
|
-
- Read files before modifying them to understand context.
|
|
513
|
-
- Only use sequential execution when one operation depends on another's output.
|
|
514
|
-
- Plan your information needs upfront and execute searches together.
|
|
515
|
-
</tool_usage>
|
|
516
|
-
|
|
517
|
-
<code_analysis>
|
|
518
|
-
Before writing any code:
|
|
519
|
-
1. Read existing files to understand context and preserve important logic.
|
|
520
|
-
2. Check dependencies exist before importing.
|
|
521
|
-
3. Match existing patterns: import style, naming conventions, component structure, error handling.
|
|
522
|
-
</code_analysis>
|
|
523
|
-
|
|
524
|
-
<code_standards>
|
|
525
|
-
- Write high-quality, general purpose solutions that work for all valid inputs.
|
|
526
|
-
- Make code immediately executable. No placeholders like "TODO: implement this".
|
|
527
|
-
- Match existing patterns in the codebase.
|
|
528
|
-
- Follow the project's established error handling approach.
|
|
529
|
-
- Verify changes work as expected before completing the task.
|
|
530
|
-
</code_standards>
|
|
531
|
-
|
|
532
|
-
<file_guidelines>
|
|
533
|
-
- ALWAYS prefer editing existing files over creating new ones.
|
|
534
|
-
- NEVER create documentation files unless explicitly requested.
|
|
535
|
-
- Use extended thinking for complex tasks rather than creating temporary files.
|
|
536
|
-
</file_guidelines>
|
|
537
|
-
|
|
538
|
-
<git_guidelines>
|
|
539
|
-
When working with git:
|
|
540
|
-
- Only create commits when explicitly requested by the user.
|
|
541
|
-
- NEVER run destructive git commands (push --force, hard reset) unless explicitly requested.
|
|
542
|
-
- NEVER skip hooks unless explicitly requested.
|
|
543
|
-
- Draft concise commit messages that focus on the "why" rather than the "what".
|
|
544
|
-
- Do not push to remote unless explicitly asked.
|
|
545
|
-
</git_guidelines>`;
|
|
546
|
-
|
|
547
|
-
// src/workflow/tools-config-store.ts
|
|
548
|
-
var stored;
|
|
549
|
-
function setToolsConfig(config) {
|
|
550
|
-
stored = config;
|
|
551
|
-
}
|
|
552
|
-
function getToolsConfig() {
|
|
553
|
-
return stored;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
345
|
// src/plugin/hooks.ts
|
|
557
|
-
async function initializeToolsConfig(input) {
|
|
558
|
-
let mcpTools = await fetchOpencodeTools(
|
|
559
|
-
input.client,
|
|
560
|
-
"gitlab",
|
|
561
|
-
"duo-chat-sonnet-4-5"
|
|
562
|
-
);
|
|
563
|
-
if (mcpTools.length === 0) {
|
|
564
|
-
mcpTools = buildFallbackTools();
|
|
565
|
-
}
|
|
566
|
-
const flowConfig = buildFlowConfig(OPENCODE_SYSTEM_PROMPT);
|
|
567
|
-
setToolsConfig({
|
|
568
|
-
mcpTools,
|
|
569
|
-
flowConfig,
|
|
570
|
-
flowConfigSchemaVersion: "v1"
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
346
|
async function createPluginHooks(input) {
|
|
574
|
-
initializeToolsConfig(input).catch(() => {
|
|
575
|
-
});
|
|
576
347
|
return {
|
|
577
348
|
config: async (config) => applyRuntimeConfig(config, input.directory),
|
|
578
349
|
"chat.message": async ({ sessionID }, { parts }) => {
|
|
@@ -746,9 +517,6 @@ var WORKFLOW_STATUS = {
|
|
|
746
517
|
function isCheckpointAction(action) {
|
|
747
518
|
return "newCheckpoint" in action && action.newCheckpoint != null;
|
|
748
519
|
}
|
|
749
|
-
function isMcpToolAction(action) {
|
|
750
|
-
return "runMCPTool" in action && action.runMCPTool != null;
|
|
751
|
-
}
|
|
752
520
|
var TURN_COMPLETE_STATUSES = /* @__PURE__ */ new Set([
|
|
753
521
|
WORKFLOW_STATUS.INPUT_REQUIRED,
|
|
754
522
|
WORKFLOW_STATUS.FINISHED,
|
|
@@ -853,15 +621,90 @@ function decodeSocketMessage(data) {
|
|
|
853
621
|
}
|
|
854
622
|
|
|
855
623
|
// src/workflow/tool-executor.ts
|
|
856
|
-
import { readFile, readdir, writeFile, mkdir, stat } from "fs/promises";
|
|
624
|
+
import { readFile, readdir, writeFile, mkdir as fsMkdir, stat } from "fs/promises";
|
|
857
625
|
import { exec } from "child_process";
|
|
858
626
|
import { resolve, dirname } from "path";
|
|
859
627
|
var ToolExecutor = class {
|
|
860
628
|
#cwd;
|
|
861
|
-
|
|
629
|
+
#client;
|
|
630
|
+
constructor(cwd, client) {
|
|
862
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
|
+
}
|
|
863
702
|
}
|
|
864
|
-
|
|
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) {
|
|
865
708
|
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
866
709
|
switch (name) {
|
|
867
710
|
case "bash":
|
|
@@ -877,21 +720,21 @@ var ToolExecutor = class {
|
|
|
877
720
|
case "grep":
|
|
878
721
|
return this.#grep(args);
|
|
879
722
|
default:
|
|
880
|
-
return { response: "", error: `Unknown tool: ${name}` };
|
|
723
|
+
return { response: "", error: `Unknown MCP tool: ${name}` };
|
|
881
724
|
}
|
|
882
725
|
}
|
|
726
|
+
// ── Handlers ──────────────────────────────────────────────────────
|
|
883
727
|
async #bash(args) {
|
|
884
728
|
const cwd = args.workdir ? resolve(this.#cwd, args.workdir) : this.#cwd;
|
|
885
729
|
const timeout = args.timeout ?? 12e4;
|
|
886
|
-
return new Promise((
|
|
730
|
+
return new Promise((res) => {
|
|
887
731
|
exec(args.command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
732
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
888
733
|
if (error) {
|
|
889
|
-
|
|
890
|
-
resolve2({ response: output2, error: error.message });
|
|
734
|
+
res({ response: output, error: error.message });
|
|
891
735
|
return;
|
|
892
736
|
}
|
|
893
|
-
|
|
894
|
-
resolve2({ response: output });
|
|
737
|
+
res({ response: output });
|
|
895
738
|
});
|
|
896
739
|
});
|
|
897
740
|
}
|
|
@@ -915,6 +758,20 @@ var ToolExecutor = class {
|
|
|
915
758
|
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
916
759
|
}
|
|
917
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
|
+
}
|
|
918
775
|
async #edit(args) {
|
|
919
776
|
try {
|
|
920
777
|
const filepath = resolve(this.#cwd, args.filePath);
|
|
@@ -944,7 +801,7 @@ var ToolExecutor = class {
|
|
|
944
801
|
async #write(args) {
|
|
945
802
|
try {
|
|
946
803
|
const filepath = resolve(this.#cwd, args.filePath);
|
|
947
|
-
await
|
|
804
|
+
await fsMkdir(dirname(filepath), { recursive: true });
|
|
948
805
|
await writeFile(filepath, args.content, "utf-8");
|
|
949
806
|
return { response: `Successfully wrote ${args.filePath}` };
|
|
950
807
|
} catch (err) {
|
|
@@ -953,38 +810,76 @@ var ToolExecutor = class {
|
|
|
953
810
|
}
|
|
954
811
|
async #glob(args) {
|
|
955
812
|
const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
|
|
956
|
-
return new Promise((
|
|
813
|
+
return new Promise((res) => {
|
|
957
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`;
|
|
958
815
|
exec(command, { cwd, timeout: 3e4 }, (error, stdout) => {
|
|
959
816
|
if (error && !stdout) {
|
|
960
817
|
exec(`ls -1 ${args.pattern} 2>/dev/null | head -200`, { cwd, timeout: 3e4 }, (_err, out) => {
|
|
961
|
-
|
|
818
|
+
res({ response: out.trim() || "No matches found" });
|
|
962
819
|
});
|
|
963
820
|
return;
|
|
964
821
|
}
|
|
965
822
|
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
966
|
-
|
|
823
|
+
res({ response: lines.length > 0 ? lines.join("\n") : "No matches found" });
|
|
967
824
|
});
|
|
968
825
|
});
|
|
969
826
|
}
|
|
970
827
|
async #grep(args) {
|
|
971
828
|
const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
|
|
972
|
-
|
|
829
|
+
const caseFlag = args.caseInsensitive ? "--ignore-case" : "";
|
|
830
|
+
return new Promise((res) => {
|
|
973
831
|
const includeFlag = args.include ? `--glob '${args.include}'` : "";
|
|
974
|
-
const
|
|
975
|
-
|
|
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) => {
|
|
976
835
|
if (stdout.trim()) {
|
|
977
|
-
|
|
836
|
+
res({ response: stdout.trim() });
|
|
978
837
|
return;
|
|
979
838
|
}
|
|
839
|
+
const grepCaseFlag = args.caseInsensitive ? "-i" : "";
|
|
980
840
|
const grepInclude = args.include ? `--include='${args.include}'` : "";
|
|
981
|
-
const grepCommand = `grep -rn ${grepInclude} '${
|
|
841
|
+
const grepCommand = `grep -rn ${grepCaseFlag} ${grepInclude} '${escapedPattern}' . 2>/dev/null | head -500`;
|
|
982
842
|
exec(grepCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_err, out) => {
|
|
983
|
-
|
|
843
|
+
res({ response: out.trim() || "No matches found" });
|
|
984
844
|
});
|
|
985
845
|
});
|
|
986
846
|
});
|
|
987
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
|
+
}
|
|
988
883
|
};
|
|
989
884
|
|
|
990
885
|
// src/workflow/session.ts
|
|
@@ -1006,8 +901,12 @@ var WorkflowSession = class {
|
|
|
1006
901
|
this.#tokenService = new WorkflowTokenService(client);
|
|
1007
902
|
this.#modelId = modelId;
|
|
1008
903
|
this.#cwd = cwd;
|
|
1009
|
-
this.#toolExecutor = new ToolExecutor(cwd);
|
|
904
|
+
this.#toolExecutor = new ToolExecutor(cwd, client);
|
|
1010
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
|
+
*/
|
|
1011
910
|
setToolsConfig(config) {
|
|
1012
911
|
this.#toolsConfig = config;
|
|
1013
912
|
}
|
|
@@ -1066,6 +965,7 @@ var WorkflowSession = class {
|
|
|
1066
965
|
mcpTools,
|
|
1067
966
|
additional_context: [],
|
|
1068
967
|
preapproved_tools: preapprovedTools,
|
|
968
|
+
// Only include flowConfig when explicitly configured (opt-in prompt override)
|
|
1069
969
|
...this.#toolsConfig?.flowConfig ? {
|
|
1070
970
|
flowConfig: this.#toolsConfig.flowConfig,
|
|
1071
971
|
flowConfigSchemaVersion: this.#toolsConfig.flowConfigSchemaVersion ?? "v1"
|
|
@@ -1094,26 +994,13 @@ var WorkflowSession = class {
|
|
|
1094
994
|
continue;
|
|
1095
995
|
}
|
|
1096
996
|
if (!event.action.requestID) continue;
|
|
1097
|
-
|
|
1098
|
-
const { name, args } = event.action.runMCPTool;
|
|
1099
|
-
const result = await this.#toolExecutor.execute(name, args);
|
|
1100
|
-
socket.send({
|
|
1101
|
-
actionResponse: {
|
|
1102
|
-
requestID: event.action.requestID,
|
|
1103
|
-
plainTextResponse: {
|
|
1104
|
-
response: result.response,
|
|
1105
|
-
error: result.error ?? ""
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
});
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
997
|
+
const result = await this.#toolExecutor.executeAction(event.action);
|
|
1111
998
|
socket.send({
|
|
1112
999
|
actionResponse: {
|
|
1113
1000
|
requestID: event.action.requestID,
|
|
1114
1001
|
plainTextResponse: {
|
|
1115
|
-
response:
|
|
1116
|
-
error:
|
|
1002
|
+
response: result.response,
|
|
1003
|
+
error: result.error ?? ""
|
|
1117
1004
|
}
|
|
1118
1005
|
}
|
|
1119
1006
|
});
|
|
@@ -1224,7 +1111,11 @@ var DuoWorkflowModel = class {
|
|
|
1224
1111
|
this.#client = client;
|
|
1225
1112
|
this.#cwd = cwd ?? process.cwd();
|
|
1226
1113
|
}
|
|
1227
|
-
/**
|
|
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
|
+
*/
|
|
1228
1119
|
setToolsConfig(config) {
|
|
1229
1120
|
this.#toolsConfig = config;
|
|
1230
1121
|
for (const session of sessions.values()) {
|
|
@@ -1328,8 +1219,7 @@ var DuoWorkflowModel = class {
|
|
|
1328
1219
|
const existing = sessions.get(key);
|
|
1329
1220
|
if (existing) return existing;
|
|
1330
1221
|
const created = new WorkflowSession(this.#client, this.modelId, this.#cwd);
|
|
1331
|
-
|
|
1332
|
-
if (config) created.setToolsConfig(config);
|
|
1222
|
+
if (this.#toolsConfig) created.setToolsConfig(this.#toolsConfig);
|
|
1333
1223
|
sessions.set(key, created);
|
|
1334
1224
|
return created;
|
|
1335
1225
|
}
|