@nail00749/agent-gvozd 0.1.8 → 0.2.1

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 CHANGED
@@ -78,16 +78,27 @@ function normalizeMcpName(name) {
78
78
  return name.replaceAll(/[^A-Za-z0-9_-]/g, "_");
79
79
  }
80
80
  function wildcardMatch(pattern, value) {
81
- let source = "^";
82
- for (const character of pattern) {
83
- if (character === "*")
84
- source += ".*";
85
- else if (character === "?")
86
- source += ".";
87
- else
88
- source += character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
81
+ let patternIndex = 0;
82
+ let valueIndex = 0;
83
+ let starPatternIndex = -1;
84
+ let restartValueIndex = 0;
85
+ while (valueIndex < value.length) {
86
+ if (patternIndex < pattern.length && pattern[patternIndex] === "*") {
87
+ starPatternIndex = patternIndex++;
88
+ restartValueIndex = valueIndex;
89
+ } else if (patternIndex < pattern.length && (pattern[patternIndex] === "?" || pattern[patternIndex] === value[valueIndex])) {
90
+ patternIndex++;
91
+ valueIndex++;
92
+ } else if (starPatternIndex >= 0) {
93
+ patternIndex = starPatternIndex + 1;
94
+ valueIndex = ++restartValueIndex;
95
+ } else {
96
+ return false;
97
+ }
89
98
  }
90
- return new RegExp(`${source}$`).test(value);
99
+ while (patternIndex < pattern.length && pattern[patternIndex] === "*")
100
+ patternIndex++;
101
+ return patternIndex === pattern.length;
91
102
  }
92
103
  function explicitMcpAccess(agent, action, resources) {
93
104
  if (resources.length === 0)
@@ -6100,6 +6111,14 @@ class FileLeaseManager {
6100
6111
  }
6101
6112
  return canonical;
6102
6113
  }
6114
+ snapshot() {
6115
+ const leases = [...this.leases.values()];
6116
+ return leases.map((lease) => this.toStatus(lease)).sort((a, b) => {
6117
+ if (a.state === "active" !== (b.state === "active"))
6118
+ return a.state === "active" ? -1 : 1;
6119
+ return a.expiresAt - b.expiresAt;
6120
+ });
6121
+ }
6103
6122
  toStatus(lease) {
6104
6123
  return {
6105
6124
  leaseId: lease.leaseId,
@@ -6354,6 +6373,190 @@ function loadConfig(projectDirectory, options = {}) {
6354
6373
  };
6355
6374
  }
6356
6375
 
6376
+ // src/permissions-rpc.ts
6377
+ import { Rpc } from "@opencode/plugin/rpc";
6378
+ var GvozdPermissions = Rpc.define({
6379
+ id: "gvozd-permissions",
6380
+ events: {},
6381
+ methods: {
6382
+ evaluate: {
6383
+ input: {
6384
+ type: "object",
6385
+ properties: {
6386
+ agent: { type: "string" },
6387
+ checks: {
6388
+ type: "array",
6389
+ items: {
6390
+ type: "object",
6391
+ properties: {
6392
+ action: { type: "string" },
6393
+ resources: { type: "array", items: { type: "string" } }
6394
+ },
6395
+ required: ["action", "resources"],
6396
+ additionalProperties: false
6397
+ }
6398
+ }
6399
+ },
6400
+ required: ["agent", "checks"],
6401
+ additionalProperties: false
6402
+ },
6403
+ output: {
6404
+ type: "object",
6405
+ properties: {
6406
+ results: {
6407
+ type: "array",
6408
+ items: {
6409
+ type: "object",
6410
+ properties: {
6411
+ action: { type: "string" },
6412
+ resource: { type: "string" },
6413
+ effect: { type: "string", enum: ["allow", "ask", "deny", "unknown"] },
6414
+ matchedRule: { type: ["string", "null"] }
6415
+ },
6416
+ required: ["action", "resource", "effect", "matchedRule"],
6417
+ additionalProperties: false
6418
+ }
6419
+ }
6420
+ },
6421
+ required: ["results"],
6422
+ additionalProperties: false
6423
+ }
6424
+ }
6425
+ }
6426
+ });
6427
+ var GvozdLeases = Rpc.define({
6428
+ id: "gvozd-leases",
6429
+ events: {},
6430
+ methods: {
6431
+ list: {
6432
+ input: {
6433
+ type: "object",
6434
+ properties: {},
6435
+ additionalProperties: false
6436
+ },
6437
+ output: {
6438
+ type: "object",
6439
+ properties: {
6440
+ leases: {
6441
+ type: "array",
6442
+ items: {
6443
+ type: "object",
6444
+ properties: {
6445
+ leaseId: { type: "string" },
6446
+ parentSessionID: { type: "string" },
6447
+ sessionID: { type: "string" },
6448
+ agent: { type: "string" },
6449
+ label: { type: "string" },
6450
+ state: { type: "string", enum: ["reserved", "active"] },
6451
+ files: { type: "array", items: { type: "string" } },
6452
+ expiresAt: { type: "number" },
6453
+ lastActivityAt: { type: "number" }
6454
+ },
6455
+ required: ["leaseId", "parentSessionID", "agent", "label", "state", "files", "expiresAt", "lastActivityAt"],
6456
+ additionalProperties: false
6457
+ }
6458
+ }
6459
+ },
6460
+ required: ["leases"],
6461
+ additionalProperties: false
6462
+ }
6463
+ }
6464
+ }
6465
+ });
6466
+ function evaluateEffect(rules, action, resource) {
6467
+ let matched;
6468
+ for (const rule of rules) {
6469
+ if (wildcardMatch(rule.action, action) && wildcardMatch(rule.resource, resource))
6470
+ matched = rule;
6471
+ }
6472
+ if (!matched)
6473
+ return { effect: "unknown", matchedRule: null };
6474
+ return { effect: matched.effect, matchedRule: `${matched.action} ${matched.resource}` };
6475
+ }
6476
+ function evaluateInput(agentRules, input) {
6477
+ const results = input.checks.flatMap((request) => (request.resources.length > 0 ? request.resources : ["*"]).map((resource) => {
6478
+ const evaluation = agentRules ? evaluateEffect(agentRules, request.action, resource) : { effect: "unknown", matchedRule: null };
6479
+ return {
6480
+ action: request.action,
6481
+ resource,
6482
+ effect: evaluation.effect,
6483
+ matchedRule: evaluation.matchedRule
6484
+ };
6485
+ }));
6486
+ return { results };
6487
+ }
6488
+
6489
+ // src/trusted-mode.ts
6490
+ import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
6491
+ var TRUST_MODES = ["balanced", "trusted", "strict"];
6492
+ var GvozdMode = Rpc2.define({
6493
+ id: "gvozd-mode",
6494
+ events: {},
6495
+ methods: {
6496
+ set: {
6497
+ input: {
6498
+ type: "object",
6499
+ properties: {
6500
+ sessionID: { type: "string" },
6501
+ mode: { type: "string", enum: [...TRUST_MODES] }
6502
+ },
6503
+ required: ["sessionID", "mode"],
6504
+ additionalProperties: false
6505
+ },
6506
+ output: {
6507
+ type: "object",
6508
+ properties: {
6509
+ mode: { type: "string", enum: [...TRUST_MODES] }
6510
+ },
6511
+ required: ["mode"],
6512
+ additionalProperties: false
6513
+ }
6514
+ },
6515
+ get: {
6516
+ input: {
6517
+ type: "object",
6518
+ properties: { sessionID: { type: "string" } },
6519
+ required: ["sessionID"],
6520
+ additionalProperties: false
6521
+ },
6522
+ output: {
6523
+ type: "object",
6524
+ properties: {
6525
+ mode: { type: "string", enum: [...TRUST_MODES] }
6526
+ },
6527
+ required: ["mode"],
6528
+ additionalProperties: false
6529
+ }
6530
+ }
6531
+ }
6532
+ });
6533
+ function modePermissions(mode) {
6534
+ if (mode === "trusted") {
6535
+ return [
6536
+ { action: "shell", resource: "*", effect: "allow" },
6537
+ { action: "edit", resource: "*", effect: "allow" },
6538
+ { action: "shell", resource: "git push --force*", effect: "deny" },
6539
+ { action: "shell", resource: "git push -f*", effect: "deny" },
6540
+ { action: "shell", resource: "git reset --hard*", effect: "deny" },
6541
+ { action: "shell", resource: "git clean*", effect: "deny" },
6542
+ { action: "shell", resource: "git filter-branch*", effect: "deny" },
6543
+ { action: "shell", resource: "git filter-repo*", effect: "deny" },
6544
+ { action: "shell", resource: "git rebase*", effect: "deny" },
6545
+ { action: "shell", resource: "git checkout --*", effect: "deny" },
6546
+ { action: "shell", resource: "git restore*", effect: "deny" },
6547
+ { action: "shell", resource: "git rebase --abort*", effect: "allow" },
6548
+ { action: "shell", resource: "git rebase --continue*", effect: "allow" }
6549
+ ];
6550
+ }
6551
+ if (mode === "strict") {
6552
+ return [
6553
+ { action: "shell", resource: "*", effect: "ask" },
6554
+ { action: "edit", resource: "*", effect: "ask" }
6555
+ ];
6556
+ }
6557
+ return [];
6558
+ }
6559
+
6357
6560
  // src/file-lease-plugin.ts
6358
6561
  var GVOZD_LEASE_TOOL = "gvozd_lease";
6359
6562
  var GVOZD_CLAIM_TOOL = "gvozd_claim";
@@ -6426,7 +6629,9 @@ function enforceFileLeasePermission(event, config, manager) {
6426
6629
  const role = roleOf(config, event.agent);
6427
6630
  if (event.action === "edit") {
6428
6631
  if (!role) {
6429
- return deny(event, event.agent ? `Agent ${event.agent} has no configured file lease role` : "OpenCode supplied no agent identity for this mutation; the write is denied");
6632
+ if (event.agent && !manager.hasActiveLeases())
6633
+ return false;
6634
+ return deny(event, event.agent ? `Agent ${event.agent} has no configured file lease role; edits are allowed only while no writer leases are active` : "OpenCode supplied no agent identity for this mutation; the write is denied");
6430
6635
  }
6431
6636
  if (role === "readonly")
6432
6637
  return deny(event, `Agent ${event.agent} is readonly and cannot mutate project files`);
@@ -6440,9 +6645,15 @@ function enforceFileLeasePermission(event, config, manager) {
6440
6645
  }
6441
6646
  if (event.action !== "shell" && event.action !== "bash")
6442
6647
  return false;
6443
- if (role === "coordinator" || role === "writer") {
6648
+ if (role === "coordinator") {
6444
6649
  return deny(event, `Agent ${event.agent} cannot use shell while file leases enforce structured mutations`);
6445
6650
  }
6651
+ if (role === "writer" && safeReadonlyShell(event.agent, event.resources)) {
6652
+ return false;
6653
+ }
6654
+ if (role === "writer") {
6655
+ return deny(event, `Agent ${event.agent} cannot use shell beyond read-only verification while file leases enforce structured mutations`);
6656
+ }
6446
6657
  if (manager.hasActiveLeases() && !(role === "readonly" && safeReadonlyShell(event.agent, event.resources))) {
6447
6658
  return deny(event, "Shell commands are paused until all active writer file leases are released");
6448
6659
  }
@@ -6706,6 +6917,55 @@ var src_default = Plugin.define({
6706
6917
  try {
6707
6918
  const fileLeases = await installFileLeaseRuntime(ctx, config, { caseInsensitive });
6708
6919
  resources.push(fileLeases);
6920
+ if (ctx.rpc && typeof ctx.rpc.register === "function") {
6921
+ const sessionModes = new Map;
6922
+ const defaultMode = () => "balanced";
6923
+ const modeRpc = await ctx.rpc.register(GvozdMode, {
6924
+ set: async (raw) => {
6925
+ const input = raw;
6926
+ sessionModes.set(input.sessionID, input.mode);
6927
+ if (typeof ctx.permission.rules === "function") {
6928
+ await ctx.permission.rules({
6929
+ sessionID: input.sessionID,
6930
+ permissions: modePermissions(input.mode)
6931
+ });
6932
+ }
6933
+ return { mode: input.mode };
6934
+ },
6935
+ get: async (raw) => {
6936
+ const input = raw;
6937
+ return { mode: sessionModes.get(input.sessionID) ?? defaultMode() };
6938
+ }
6939
+ });
6940
+ resources.push(modeRpc);
6941
+ const leasesRpc = await ctx.rpc.register(GvozdLeases, {
6942
+ list: async () => {
6943
+ const output = {
6944
+ leases: fileLeases.manager.snapshot().map((lease) => ({
6945
+ leaseId: lease.leaseId,
6946
+ parentSessionID: lease.parentSessionID,
6947
+ ...lease.sessionID ? { sessionID: lease.sessionID } : {},
6948
+ agent: lease.agent,
6949
+ label: lease.label,
6950
+ state: lease.state,
6951
+ files: [...lease.files],
6952
+ expiresAt: lease.expiresAt,
6953
+ lastActivityAt: lease.lastActivityAt
6954
+ }))
6955
+ };
6956
+ return output;
6957
+ }
6958
+ });
6959
+ resources.push(leasesRpc);
6960
+ const permissionRpc = await ctx.rpc.register(GvozdPermissions, {
6961
+ evaluate: async (raw) => {
6962
+ const input = raw;
6963
+ const agent = config.agents[input.agent];
6964
+ return evaluateInput(agent && !agent.disabled ? agent.permissions : undefined, input);
6965
+ }
6966
+ });
6967
+ resources.push(permissionRpc);
6968
+ }
6709
6969
  const agentTransform = await ctx.agent.transform((agents) => {
6710
6970
  applyAgentConfiguration(agents, config, models.data, mcpServers);
6711
6971
  });