@nail00749/agent-gvozd 0.1.7 → 0.2.0

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