Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
60be837
feat(llm): integrate GitLab DWS tool approval with permission system
vglafirov Mar 29, 2026
ee90930
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 29, 2026
4c160a4
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 30, 2026
c190a28
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 30, 2026
42c7ca3
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 30, 2026
68d3235
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 30, 2026
91a9f96
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 30, 2026
b3d99e3
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 31, 2026
5609fee
refactor(llm): address review feedback on DWS approval integration
vglafirov Mar 31, 2026
bb80f30
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 31, 2026
4179a5d
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 31, 2026
12f9923
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 31, 2026
fe343d8
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Mar 31, 2026
2d3e70b
Merge branch 'dev' into feat/gitlab-dws-approval-integration
rekram1-node Apr 1, 2026
20e6427
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
5b18a01
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
13420d1
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
60ceee0
Merge branch 'dev' into feat/gitlab-dws-approval-integration
rekram1-node Apr 1, 2026
36be8d6
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
ba3b4e3
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
245684f
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
f164c2a
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
7524186
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 1, 2026
6825008
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
822ddd5
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
2644a8f
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
636f9fd
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
1477d7b
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
236c752
fix(gitlab): auto-approve previously approved DWS tools to prevent ap…
vglafirov Apr 2, 2026
ed879a1
fix: bump provider version
vglafirov Apr 2, 2026
4f7f2e8
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
84c0e96
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
4bd6d84
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
3f25f79
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
8e11831
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 2, 2026
60f14fd
fix: skip tool parts that already completed
vglafirov Apr 2, 2026
7c7c428
fix: skip provider executed tools
vglafirov Apr 2, 2026
b92393a
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 3, 2026
113594b
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 3, 2026
7e17b0c
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 3, 2026
a2f8621
merge: sync with upstream/dev and restore workflowDefinition passthrough
vglafirov Apr 5, 2026
1219566
fix: gen types
vglafirov Apr 5, 2026
3312d3f
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
06bb9a8
fix: fix after the review
vglafirov Apr 6, 2026
b4bcbfc
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
50f7124
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
3747eed
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
18ddb2b
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
104c4b3
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 6, 2026
78e1390
Merge branch 'dev' into feat/gitlab-dws-approval-integration
rekram1-node Apr 7, 2026
838a7f4
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
9eadc49
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
8991c70
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
a34b859
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
8057397
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
d72cb78
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
1c9918d
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
61b223b
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
1042141
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 7, 2026
1a56160
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
5e6ab6e
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
420b394
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
c1c04fa
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
003c0a0
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
9d304a6
Merge branch 'dev' into feat/gitlab-dws-approval-integration
vglafirov Apr 8, 2026
484e40a
fix: bump gitlab-ai-provider version
vglafirov Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(llm): integrate GitLab DWS tool approval with permission system
Wire approvalHandler and sessionPreapprovedTools on GitLabWorkflowLanguageModel
so that DWS tool-call approval checkpoints surface as opencode permission prompts.

- Set sessionPreapprovedTools from the merged agent+session ruleset so tools
  without an explicit 'ask' rule are pre-approved for the DWS session
- Set approvalHandler (wrapped with Instance.bind to restore ALS context)
  that delegates to Permission.ask with permission type 'workflow_tool_approval'
- Capture the Bus Replied event to detect 'always' replies and extend
  sessionPreapprovedTools for subsequent DWS turns
- Return { approved: true/false } so approveAndResume reconnects with the
  correct approval or rejection payload
- Bump gitlab-ai-provider to 6.1.0 which includes the approvalPending
  stream-close deferral and rejection forwarding fixes
- Add preapprovedWorkflowTools helper (exported for testing) that filters
  tools using findLast rule precedence matching ask-only exclusion
  • Loading branch information
vglafirov committed Mar 29, 2026
commit 60be8377a3d782dbabfffd549c4452883a88ee8b
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
"drizzle-orm": "catalog:",
"effect": "catalog:",
"fuzzysort": "3.1.0",
"gitlab-ai-provider": "6.0.0",
"gitlab-ai-provider": "6.1.0",
"glob": "13.0.5",
"google-auth-library": "10.5.0",
"gray-matter": "4.0.3",
Expand Down
45 changes: 45 additions & 0 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system"
import { Flag } from "@/flag/flag"
import { Permission } from "@/permission"
import { PermissionID } from "@/permission/schema"
import { Bus } from "@/bus"
import { Wildcard } from "@/util/wildcard"
import { SessionID } from "@/session/schema"
import { Auth } from "@/auth"
import { Installation } from "@/installation"

Expand Down Expand Up @@ -235,6 +239,40 @@ export namespace LLM {
return { result: "", error: e.message ?? String(e) }
}
}

const ruleset = Permission.merge(input.agent.permission ?? [], input.permission ?? [])
workflowModel.sessionPreapprovedTools = preapprovedWorkflowTools(Object.keys(tools), ruleset)

workflowModel.approvalHandler = Instance.bind(async (approvalTools) => {
const id = PermissionID.ascending()
let reply: Permission.Reply | undefined
let unsub: (() => void) | undefined
try {
unsub = Bus.subscribe(Permission.Event.Replied, (evt) => {
if (evt.properties.requestID === id) reply = evt.properties.reply
})
await Permission.ask({
id,
sessionID: SessionID.zod.parse(input.sessionID),
Comment thread
vglafirov marked this conversation as resolved.
Outdated
permission: "workflow_tool_approval",
patterns: approvalTools.map((t) => t.name),
metadata: { tools: approvalTools },
always: approvalTools.map((t) => t.name),
ruleset: [],
})
if (reply === "always") {
workflowModel.sessionPreapprovedTools = [
...workflowModel.sessionPreapprovedTools,
...approvalTools.map((t) => t.name),
]
}
return { approved: true }
} catch {
return { approved: false }
} finally {
unsub?.()
}
})
}

return streamText({
Expand Down Expand Up @@ -327,6 +365,13 @@ export namespace LLM {
return input.tools
}

export function preapprovedWorkflowTools(tools: string[], ruleset: Permission.Ruleset): string[] {
return tools.filter((name) => {
const match = ruleset.findLast((rule) => Wildcard.match(name, rule.permission))
return !match || match.action !== "ask"
})
}

Comment thread
vglafirov marked this conversation as resolved.
Outdated
// Check if messages contain any tool-call content
// Used to determine if a dummy tool should be added for LiteLLM proxy compatibility
export function hasToolCalls(messages: ModelMessage[]): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, test } from "bun:test"
import { LLM } from "../../src/session/llm"
import type { Permission } from "../../src/permission"

describe("LLM.preapprovedWorkflowTools", () => {
test("returns all tools when ruleset is empty", () => {
const tools = ["read_file", "write_file", "run_command"]
const result = LLM.preapprovedWorkflowTools(tools, [])
expect(result).toEqual(tools)
})

test("excludes tools with explicit ask rule", () => {
const tools = ["read_file", "run_command", "write_file"]
const ruleset: Permission.Ruleset = [{ permission: "run_command", pattern: "*", action: "ask" }]
const result = LLM.preapprovedWorkflowTools(tools, ruleset)
expect(result).not.toContain("run_command")
expect(result).toContain("read_file")
expect(result).toContain("write_file")
})

test("excludes tools matched by wildcard ask rule", () => {
const tools = ["read_file", "write_file", "run_command"]
const ruleset: Permission.Ruleset = [{ permission: "*", pattern: "*", action: "ask" }]
const result = LLM.preapprovedWorkflowTools(tools, ruleset)
expect(result).toEqual([])
})

test("allows tools with allow rule overriding ask", () => {
const tools = ["run_command"]
const ruleset: Permission.Ruleset = [
{ permission: "*", pattern: "*", action: "ask" },
{ permission: "run_command", pattern: "*", action: "allow" },
]
const result = LLM.preapprovedWorkflowTools(tools, ruleset)
expect(result).toContain("run_command")
})

test("preapproves tools with only deny rules (deny is handled by resolveTools, not here)", () => {
const tools = ["read_file"]
const ruleset: Permission.Ruleset = [{ permission: "read_file", pattern: "*", action: "deny" }]
const result = LLM.preapprovedWorkflowTools(tools, ruleset)
expect(result).toContain("read_file")
})
})
Loading