@nail00749/agent-gvozd 0.1.2 → 0.1.3
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/defaults/agents/devops.jsonc +918 -29
- package/defaults/agents/git.jsonc +1035 -36
- package/defaults/agents/verifier.jsonc +873 -20
- package/defaults/prompts/git.md +3 -1
- package/defaults/prompts/master.md +10 -1
- package/defaults/prompts/verifier.md +3 -1
- package/defaults/schema.json +11 -1
- package/dist/cli.js +467 -8
- package/dist/index.js +342 -258
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,69 @@
|
|
|
2
2
|
// src/index.ts
|
|
3
3
|
import { Model, Plugin } from "@opencode/plugin";
|
|
4
4
|
|
|
5
|
+
// src/tool-permissions.ts
|
|
6
|
+
function family(command, ...variants) {
|
|
7
|
+
return [command, ...variants].map((entry) => ({
|
|
8
|
+
exact: entry,
|
|
9
|
+
wildcard: `${entry} *`
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
var INSPECTION_COMMANDS = [
|
|
13
|
+
...family("pwd", "true", "test"),
|
|
14
|
+
...family("cat", "head", "tail", "wc", "sort", "uniq"),
|
|
15
|
+
...family("grep", "rg", "find", "diff", "cmp"),
|
|
16
|
+
...family("ls", "du", "df", "stat", "file", "realpath", "basename", "dirname"),
|
|
17
|
+
...family("shasum", "sha256sum", "md5sum"),
|
|
18
|
+
...family("uname", "whoami", "hostname", "date", "printenv"),
|
|
19
|
+
...family("which", "command -v"),
|
|
20
|
+
...family("mktemp"),
|
|
21
|
+
...family("tr", "cut", "paste", "column"),
|
|
22
|
+
...family("node --version", "python3 --version", "python --version", "deno --version")
|
|
23
|
+
];
|
|
24
|
+
var TOOLCHAIN_COMMANDS = [
|
|
25
|
+
...family("bun test", "bun run test", "bun --version"),
|
|
26
|
+
...family("bun run typecheck", "bun run lint", "bun run build", "bun run check"),
|
|
27
|
+
...family("tsc --noEmit", "npx tsc --noEmit"),
|
|
28
|
+
...family("eslint", "biome check", "prettier --check"),
|
|
29
|
+
...family("npm test", "npm run test", "npm run typecheck", "npm run lint", "npm run build"),
|
|
30
|
+
...family("pnpm test", "pnpm run test", "pnpm run build"),
|
|
31
|
+
...family("yarn test", "yarn build"),
|
|
32
|
+
...family("vitest run", "jest", "playwright test"),
|
|
33
|
+
...family("cargo check", "cargo test", "cargo build", "cargo clippy", "cargo fmt --check", "cargo --version"),
|
|
34
|
+
...family("go build ./...", "go test ./...", "go vet ./...", "go version"),
|
|
35
|
+
...family("pytest", "python3 -m pytest", "python -m pytest"),
|
|
36
|
+
...family("ruff check", "mypy", "pyright"),
|
|
37
|
+
...family("mvn test", "mvn verify", "gradle test", "gradle check", "./gradlew test", "./gradlew check"),
|
|
38
|
+
...family("make test", "make check", "make build", "make --version"),
|
|
39
|
+
...family("just --list")
|
|
40
|
+
];
|
|
41
|
+
var GIT_READONLY_COMMANDS = [
|
|
42
|
+
...family("git status", "git status --short", "git status --short --branch", "git status --porcelain", "git status --porcelain=v1 --branch"),
|
|
43
|
+
...family("git diff", "git diff --stat", "git diff --cached", "git diff --check"),
|
|
44
|
+
...family("git log", "git show", "git reflog"),
|
|
45
|
+
...family("git rev-parse", "git rev-list", "git show-ref", "git cat-file", "git symbolic-ref"),
|
|
46
|
+
...family("git ls-files", "git ls-remote", "git grep"),
|
|
47
|
+
...family("git branch", "git remote", "git stash list", "git tag", "git describe"),
|
|
48
|
+
...family("git config --get", "git config --get-regexp"),
|
|
49
|
+
...family("git -C")
|
|
50
|
+
];
|
|
51
|
+
var GIT_MUTATING_COMMANDS = [
|
|
52
|
+
...family("git add", "git rm --cached"),
|
|
53
|
+
...family("git commit", "git merge --ff-only", "git merge --no-ff"),
|
|
54
|
+
...family("git push", "git fetch", "git pull --ff-only"),
|
|
55
|
+
...family("git tag -a", "git tag -v", "git tag --list"),
|
|
56
|
+
...family("git stash", "git cherry-pick", "git revert"),
|
|
57
|
+
...family("git switch", "git checkout -b", "git worktree list", "git worktree add")
|
|
58
|
+
];
|
|
59
|
+
var GIT_ENV_PREFIXES = ["GIT_OPTIONAL_LOCKS=0"];
|
|
60
|
+
function withEnvPrefixes(rule, prefixes = GIT_ENV_PREFIXES) {
|
|
61
|
+
return prefixes.map((prefix) => ({
|
|
62
|
+
action: rule.action,
|
|
63
|
+
resource: `${prefix} ${rule.resource}`,
|
|
64
|
+
effect: rule.effect
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
5
68
|
// src/agent-permissions.ts
|
|
6
69
|
function normalizeMcpName(name) {
|
|
7
70
|
return name.replaceAll(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -45,12 +108,21 @@ function buildAgentPermissions(agent, mcpServers) {
|
|
|
45
108
|
});
|
|
46
109
|
result.push(...agent.permissions.filter((rule) => rule.action.startsWith(prefix)));
|
|
47
110
|
}
|
|
111
|
+
for (const rule of agent.permissions) {
|
|
112
|
+
if (rule.action !== "shell")
|
|
113
|
+
continue;
|
|
114
|
+
if (!/(^|\s|["'])git(?:$|\s)/.test(rule.resource))
|
|
115
|
+
continue;
|
|
116
|
+
if (rule.resource.startsWith("GIT_"))
|
|
117
|
+
continue;
|
|
118
|
+
result.push(...withEnvPrefixes(rule));
|
|
119
|
+
}
|
|
48
120
|
return result;
|
|
49
121
|
}
|
|
50
122
|
|
|
51
123
|
// src/config.ts
|
|
52
|
-
import { existsSync as
|
|
53
|
-
import { dirname as
|
|
124
|
+
import { existsSync as existsSync3, lstatSync as lstatSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, realpathSync as realpathSync3 } from "fs";
|
|
125
|
+
import { dirname as dirname3, isAbsolute as isAbsolute4, join as join3, relative as relative3, resolve as resolve4 } from "path";
|
|
54
126
|
import { fileURLToPath } from "url";
|
|
55
127
|
|
|
56
128
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
@@ -5686,226 +5758,10 @@ function computeProjectTrustToken(projectDirectory) {
|
|
|
5686
5758
|
return `sha256:${hash.digest("hex")}`;
|
|
5687
5759
|
}
|
|
5688
5760
|
|
|
5689
|
-
// src/config.ts
|
|
5690
|
-
var permissionSchema = object({
|
|
5691
|
-
action: string2().min(1),
|
|
5692
|
-
resource: string2().min(1),
|
|
5693
|
-
effect: _enum(["allow", "ask", "deny"])
|
|
5694
|
-
}).strict();
|
|
5695
|
-
var modelRefSchema = string2().min(1).regex(/^[^/#\s]+\/[^#\s]+(?:#[^#\s]+)?$/, "Expected provider/model or provider/model#variant");
|
|
5696
|
-
var agentIdSchema = string2().regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/, "Expected a filesystem-safe agent ID");
|
|
5697
|
-
var fileLeaseRoleSchema = _enum(["coordinator", "writer", "readonly"]);
|
|
5698
|
-
var agentPatchSchema = object({
|
|
5699
|
-
description: string2().min(1).optional(),
|
|
5700
|
-
mode: _enum(["primary", "subagent", "all"]).optional(),
|
|
5701
|
-
models: array(modelRefSchema).min(1).optional(),
|
|
5702
|
-
prompt: string2().min(1).optional(),
|
|
5703
|
-
skills: array(string2().min(1)).optional(),
|
|
5704
|
-
mcp: array(string2().min(1)).optional(),
|
|
5705
|
-
permissions: array(permissionSchema).optional(),
|
|
5706
|
-
fileLease: fileLeaseRoleSchema.optional(),
|
|
5707
|
-
disabled: boolean2().optional()
|
|
5708
|
-
}).strict();
|
|
5709
|
-
var rootPatchSchema = object({
|
|
5710
|
-
$schema: string2().min(1).optional(),
|
|
5711
|
-
defaultAgent: agentIdSchema.optional(),
|
|
5712
|
-
agentsDirectory: string2().min(1).optional(),
|
|
5713
|
-
agents: record(agentIdSchema, agentPatchSchema).optional()
|
|
5714
|
-
}).strict();
|
|
5715
|
-
var resolvedAgentSchema = agentPatchSchema.extend({
|
|
5716
|
-
description: string2().min(1),
|
|
5717
|
-
mode: _enum(["primary", "subagent", "all"]),
|
|
5718
|
-
models: array(modelRefSchema).min(1),
|
|
5719
|
-
prompt: string2().min(1),
|
|
5720
|
-
skills: array(string2().min(1)),
|
|
5721
|
-
mcp: array(string2().min(1)),
|
|
5722
|
-
permissions: array(permissionSchema),
|
|
5723
|
-
fileLease: fileLeaseRoleSchema,
|
|
5724
|
-
disabled: boolean2()
|
|
5725
|
-
});
|
|
5726
|
-
var SAFE_UNTRUSTED_AGENT_FIELDS = new Set(["description"]);
|
|
5727
|
-
function assertTrustedProjectPatch(patch, sourcePath, id, trusted, knownAgents) {
|
|
5728
|
-
if (trusted)
|
|
5729
|
-
return;
|
|
5730
|
-
const fields = Object.keys(patch).filter((field) => !SAFE_UNTRUSTED_AGENT_FIELDS.has(field));
|
|
5731
|
-
if (fields.length === 0 && knownAgents.has(id))
|
|
5732
|
-
return;
|
|
5733
|
-
throw new Error(`Untrusted project config ${sourcePath} cannot override ${fields.join(", ") || `unknown agent ${id}`}. ` + `Set ${PROJECT_TRUST_ENV} to the exact token returned by computeProjectTrustToken() after reviewing these files.`);
|
|
5734
|
-
}
|
|
5735
|
-
function readJsonc2(path) {
|
|
5736
|
-
const errors = [];
|
|
5737
|
-
const value = parse2(readFileSync2(path, "utf8"), errors, {
|
|
5738
|
-
allowTrailingComma: true,
|
|
5739
|
-
disallowComments: false
|
|
5740
|
-
});
|
|
5741
|
-
if (errors.length > 0) {
|
|
5742
|
-
const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
|
|
5743
|
-
throw new Error(`Invalid JSONC in ${path}: ${details}`);
|
|
5744
|
-
}
|
|
5745
|
-
return value;
|
|
5746
|
-
}
|
|
5747
|
-
function assertWithin(base, target, label) {
|
|
5748
|
-
const child = relative2(base, target);
|
|
5749
|
-
if (child === "" || !child.startsWith("..") && !isAbsolute3(child))
|
|
5750
|
-
return;
|
|
5751
|
-
throw new Error(`${label} must stay inside ${base}: ${target}`);
|
|
5752
|
-
}
|
|
5753
|
-
function resolvePrompt(patch, sourcePath, layerDirectory) {
|
|
5754
|
-
if (!patch.prompt)
|
|
5755
|
-
return patch;
|
|
5756
|
-
const prompt = resolve3(dirname2(sourcePath), patch.prompt);
|
|
5757
|
-
assertWithin(layerDirectory, prompt, "Agent prompt");
|
|
5758
|
-
if (!existsSync2(prompt))
|
|
5759
|
-
throw new Error(`Agent prompt is missing: ${prompt}`);
|
|
5760
|
-
const promptStat = lstatSync2(prompt);
|
|
5761
|
-
if (promptStat.isSymbolicLink() || !promptStat.isFile())
|
|
5762
|
-
throw new Error(`Agent prompt must be a regular non-symlink file: ${prompt}`);
|
|
5763
|
-
const canonical = realpathSync2(prompt);
|
|
5764
|
-
assertWithin(realpathSync2(layerDirectory), canonical, "Agent prompt");
|
|
5765
|
-
const promptContent = readFileSync2(canonical, "utf8");
|
|
5766
|
-
return { ...patch, prompt: canonical, promptContent };
|
|
5767
|
-
}
|
|
5768
|
-
function loadLayer(directory, rootFileName, required, projectPolicy) {
|
|
5769
|
-
const rootPath = join3(directory, rootFileName);
|
|
5770
|
-
if (!existsSync2(rootPath)) {
|
|
5771
|
-
if (required)
|
|
5772
|
-
throw new Error(`Required config is missing: ${rootPath}`);
|
|
5773
|
-
return { agents: {}, sources: [] };
|
|
5774
|
-
}
|
|
5775
|
-
const root = rootPatchSchema.parse(readJsonc2(rootPath));
|
|
5776
|
-
if (projectPolicy && !projectPolicy.trusted) {
|
|
5777
|
-
const restricted = ["defaultAgent", "agentsDirectory"].filter((field) => Object.prototype.hasOwnProperty.call(root, field));
|
|
5778
|
-
if (restricted.length > 0)
|
|
5779
|
-
throw new Error(`Untrusted project config ${rootPath} cannot override ${restricted.join(", ")}`);
|
|
5780
|
-
}
|
|
5781
|
-
const agents = {};
|
|
5782
|
-
for (const [id, patch] of Object.entries(root.agents ?? {})) {
|
|
5783
|
-
if (projectPolicy)
|
|
5784
|
-
assertTrustedProjectPatch(patch, rootPath, id, projectPolicy.trusted, projectPolicy.knownAgents);
|
|
5785
|
-
agents[id] = resolvePrompt(patch, rootPath, directory);
|
|
5786
|
-
}
|
|
5787
|
-
const agentsDirectory = resolve3(directory, root.agentsDirectory ?? "agents");
|
|
5788
|
-
assertWithin(directory, agentsDirectory, "agentsDirectory");
|
|
5789
|
-
if (existsSync2(agentsDirectory)) {
|
|
5790
|
-
const directoryStat = lstatSync2(agentsDirectory);
|
|
5791
|
-
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
5792
|
-
throw new Error(`agentsDirectory must be a regular directory: ${agentsDirectory}`);
|
|
5793
|
-
}
|
|
5794
|
-
assertWithin(realpathSync2(directory), realpathSync2(agentsDirectory), "agentsDirectory");
|
|
5795
|
-
for (const entry of readdirSync2(agentsDirectory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5796
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonc"))
|
|
5797
|
-
continue;
|
|
5798
|
-
const id = entry.name.slice(0, -".jsonc".length);
|
|
5799
|
-
agentIdSchema.parse(id);
|
|
5800
|
-
const agentPath = join3(agentsDirectory, entry.name);
|
|
5801
|
-
const patch = agentPatchSchema.parse(readJsonc2(agentPath));
|
|
5802
|
-
if (projectPolicy)
|
|
5803
|
-
assertTrustedProjectPatch(patch, agentPath, id, projectPolicy.trusted, projectPolicy.knownAgents);
|
|
5804
|
-
agents[id] = mergeAgent(agents[id], resolvePrompt(patch, agentPath, directory));
|
|
5805
|
-
}
|
|
5806
|
-
}
|
|
5807
|
-
return {
|
|
5808
|
-
defaultAgent: root.defaultAgent,
|
|
5809
|
-
agents,
|
|
5810
|
-
sources: [rootPath]
|
|
5811
|
-
};
|
|
5812
|
-
}
|
|
5813
|
-
function mergeAgent(base, override) {
|
|
5814
|
-
return { ...base ?? {}, ...override };
|
|
5815
|
-
}
|
|
5816
|
-
function resolveAgentConfig(patch) {
|
|
5817
|
-
const parsed = agentPatchSchema.extend({ promptContent: string2().optional() }).parse(patch);
|
|
5818
|
-
const { promptContent, ...agentPatch } = parsed;
|
|
5819
|
-
const resolved = resolvedAgentSchema.parse({
|
|
5820
|
-
skills: [],
|
|
5821
|
-
mcp: [],
|
|
5822
|
-
permissions: [],
|
|
5823
|
-
fileLease: "readonly",
|
|
5824
|
-
disabled: false,
|
|
5825
|
-
...agentPatch
|
|
5826
|
-
});
|
|
5827
|
-
return promptContent === undefined ? resolved : { ...resolved, promptContent };
|
|
5828
|
-
}
|
|
5829
|
-
function findPackageRoot() {
|
|
5830
|
-
let current = dirname2(fileURLToPath(import.meta.url));
|
|
5831
|
-
while (true) {
|
|
5832
|
-
if (existsSync2(join3(current, "defaults", "default.jsonc")))
|
|
5833
|
-
return current;
|
|
5834
|
-
const parent = dirname2(current);
|
|
5835
|
-
if (parent === current)
|
|
5836
|
-
break;
|
|
5837
|
-
current = parent;
|
|
5838
|
-
}
|
|
5839
|
-
throw new Error("Unable to locate agent-gvozd package defaults");
|
|
5840
|
-
}
|
|
5841
|
-
function findProjectRoot(start) {
|
|
5842
|
-
let current = resolve3(start);
|
|
5843
|
-
while (true) {
|
|
5844
|
-
const git = join3(current, ".git");
|
|
5845
|
-
if (existsSync2(git))
|
|
5846
|
-
return current;
|
|
5847
|
-
const parent = dirname2(current);
|
|
5848
|
-
if (parent === current)
|
|
5849
|
-
return resolve3(start);
|
|
5850
|
-
current = parent;
|
|
5851
|
-
}
|
|
5852
|
-
}
|
|
5853
|
-
function loadConfig(projectDirectory, options = {}) {
|
|
5854
|
-
const projectRoot = findProjectRoot(projectDirectory);
|
|
5855
|
-
const packageRoot = findPackageRoot();
|
|
5856
|
-
const projectConfigDirectory = join3(projectRoot, "docs", ".gvozd");
|
|
5857
|
-
const globalConfigDirectory = join3(resolveOpenCodeConfigRoot(options.env, options.platform, options.home, options.configRoot), "gvozd");
|
|
5858
|
-
const baseLayers = [
|
|
5859
|
-
loadLayer(join3(packageRoot, "defaults"), "default.jsonc", true),
|
|
5860
|
-
loadLayer(globalConfigDirectory, "config.jsonc", false)
|
|
5861
|
-
];
|
|
5862
|
-
const knownAgents = new Set(baseLayers.flatMap((layer) => Object.keys(layer.agents)));
|
|
5863
|
-
const includeProject = options.includeProject !== false;
|
|
5864
|
-
const suppliedToken = includeProject ? options.projectTrustToken ?? (options.env ?? process.env)[PROJECT_TRUST_ENV] : undefined;
|
|
5865
|
-
const trustProjectConfig = includeProject && suppliedToken !== undefined && suppliedToken === computeProjectTrustToken(projectRoot);
|
|
5866
|
-
const projectLayer = includeProject ? loadLayer(projectConfigDirectory, "config.jsonc", false, { trusted: trustProjectConfig, knownAgents }) : undefined;
|
|
5867
|
-
if (trustProjectConfig && suppliedToken !== computeProjectTrustToken(projectRoot)) {
|
|
5868
|
-
throw new Error("Project configuration changed while its trust token was being validated; review it and compute a new token");
|
|
5869
|
-
}
|
|
5870
|
-
const layers = [...baseLayers, ...projectLayer ? [projectLayer] : []];
|
|
5871
|
-
let defaultAgent;
|
|
5872
|
-
const agents = {};
|
|
5873
|
-
for (const layer of layers) {
|
|
5874
|
-
defaultAgent = layer.defaultAgent ?? defaultAgent;
|
|
5875
|
-
for (const [id, patch] of Object.entries(layer.agents)) {
|
|
5876
|
-
agents[id] = mergeAgent(agents[id], patch);
|
|
5877
|
-
}
|
|
5878
|
-
}
|
|
5879
|
-
if (!defaultAgent)
|
|
5880
|
-
throw new Error("defaultAgent is not configured");
|
|
5881
|
-
const resolvedAgents = Object.fromEntries(Object.entries(agents).map(([id, patch]) => [
|
|
5882
|
-
id,
|
|
5883
|
-
(() => {
|
|
5884
|
-
const agent = resolveAgentConfig(patch);
|
|
5885
|
-
if (agent.promptContent === undefined)
|
|
5886
|
-
throw new Error(`Agent prompt snapshot is missing after configuration load: ${agent.prompt}`);
|
|
5887
|
-
return agent;
|
|
5888
|
-
})()
|
|
5889
|
-
]));
|
|
5890
|
-
const defaultConfig = resolvedAgents[defaultAgent];
|
|
5891
|
-
if (!defaultConfig || defaultConfig.disabled || defaultConfig.mode === "subagent") {
|
|
5892
|
-
throw new Error(`defaultAgent must reference an enabled primary agent: ${defaultAgent}`);
|
|
5893
|
-
}
|
|
5894
|
-
return {
|
|
5895
|
-
defaultAgent,
|
|
5896
|
-
agents: resolvedAgents,
|
|
5897
|
-
packageRoot,
|
|
5898
|
-
projectRoot,
|
|
5899
|
-
projectConfigDirectory,
|
|
5900
|
-
globalConfigDirectory,
|
|
5901
|
-
sources: layers.flatMap((layer) => layer.sources)
|
|
5902
|
-
};
|
|
5903
|
-
}
|
|
5904
|
-
|
|
5905
5761
|
// src/file-leases.ts
|
|
5906
5762
|
import { randomUUID } from "crypto";
|
|
5907
|
-
import { existsSync as
|
|
5908
|
-
import { basename, dirname as
|
|
5763
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, realpathSync as realpathSync2, statSync } from "fs";
|
|
5764
|
+
import { basename, dirname as dirname2, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve3, sep } from "path";
|
|
5909
5765
|
|
|
5910
5766
|
class LeaseError extends Error {
|
|
5911
5767
|
code;
|
|
@@ -5941,11 +5797,11 @@ function assertPositiveDuration(value, label) {
|
|
|
5941
5797
|
return value;
|
|
5942
5798
|
}
|
|
5943
5799
|
function isWithin(root, target) {
|
|
5944
|
-
const child =
|
|
5945
|
-
return child !== "" && !child.startsWith(`..${sep}`) && child !== ".." && !
|
|
5800
|
+
const child = relative2(root, target);
|
|
5801
|
+
return child !== "" && !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute3(child);
|
|
5946
5802
|
}
|
|
5947
5803
|
function displayPath(root, target) {
|
|
5948
|
-
return
|
|
5804
|
+
return relative2(root, target).replaceAll("\\", "/");
|
|
5949
5805
|
}
|
|
5950
5806
|
|
|
5951
5807
|
class FileLeaseManager {
|
|
@@ -5959,7 +5815,7 @@ class FileLeaseManager {
|
|
|
5959
5815
|
fileOwners = new Map;
|
|
5960
5816
|
sessionOwners = new Map;
|
|
5961
5817
|
constructor(options) {
|
|
5962
|
-
this.projectRoot =
|
|
5818
|
+
this.projectRoot = realpathSync2(options.projectRoot);
|
|
5963
5819
|
if (!statSync(this.projectRoot).isDirectory())
|
|
5964
5820
|
throw new Error(`Project root is not a directory: ${this.projectRoot}`);
|
|
5965
5821
|
this.reservationTtlMs = assertPositiveDuration(options.reservationTtlMs ?? DEFAULT_RESERVATION_TTL_MS, "reservationTtlMs");
|
|
@@ -6175,7 +6031,7 @@ class FileLeaseManager {
|
|
|
6175
6031
|
const value = input.trim();
|
|
6176
6032
|
if (value === "" || value.includes("\x00"))
|
|
6177
6033
|
throw new LeaseError("INVALID_PATH", "File path must not be empty");
|
|
6178
|
-
if (!allowAbsolute &&
|
|
6034
|
+
if (!allowAbsolute && isAbsolute3(value))
|
|
6179
6035
|
throw new LeaseError("INVALID_PATH", `Absolute lease path is not allowed: ${value}`);
|
|
6180
6036
|
if (value.includes("*") || value.includes("?")) {
|
|
6181
6037
|
throw new LeaseError("INVALID_PATH", `Lease paths must name exact files, not patterns: ${value}`);
|
|
@@ -6183,14 +6039,14 @@ class FileLeaseManager {
|
|
|
6183
6039
|
if (value.endsWith("/") || value.endsWith("\\")) {
|
|
6184
6040
|
throw new LeaseError("INVALID_PATH", `Lease paths must name files, not directories: ${value}`);
|
|
6185
6041
|
}
|
|
6186
|
-
const candidate =
|
|
6187
|
-
if ((!allowAbsolute || !
|
|
6042
|
+
const candidate = resolve3(this.projectRoot, value);
|
|
6043
|
+
if ((!allowAbsolute || !isAbsolute3(value)) && !isWithin(this.projectRoot, candidate)) {
|
|
6188
6044
|
throw new LeaseError("INVALID_PATH", `File must stay inside the project root: ${value}`);
|
|
6189
6045
|
}
|
|
6190
6046
|
const code = allowAbsolute ? "OUT_OF_SCOPE" : "INVALID_PATH";
|
|
6191
|
-
if (
|
|
6047
|
+
if (existsSync2(candidate)) {
|
|
6192
6048
|
try {
|
|
6193
|
-
const target =
|
|
6049
|
+
const target = lstatSync2(candidate);
|
|
6194
6050
|
if (!target.isSymbolicLink() && !target.isFile()) {
|
|
6195
6051
|
throw new LeaseError(code, `Lease targets must be regular files: ${value}`);
|
|
6196
6052
|
}
|
|
@@ -6203,8 +6059,8 @@ class FileLeaseManager {
|
|
|
6203
6059
|
}
|
|
6204
6060
|
let ancestor = candidate;
|
|
6205
6061
|
const suffix = [];
|
|
6206
|
-
while (!
|
|
6207
|
-
const parent =
|
|
6062
|
+
while (!existsSync2(ancestor)) {
|
|
6063
|
+
const parent = dirname2(ancestor);
|
|
6208
6064
|
if (parent === ancestor)
|
|
6209
6065
|
throw new LeaseError("INVALID_PATH", `Cannot resolve file path: ${value}`);
|
|
6210
6066
|
suffix.unshift(basename(ancestor));
|
|
@@ -6212,16 +6068,16 @@ class FileLeaseManager {
|
|
|
6212
6068
|
}
|
|
6213
6069
|
let canonicalAncestor;
|
|
6214
6070
|
try {
|
|
6215
|
-
canonicalAncestor =
|
|
6071
|
+
canonicalAncestor = realpathSync2(ancestor);
|
|
6216
6072
|
} catch {
|
|
6217
6073
|
throw new LeaseError("INVALID_PATH", `Cannot canonicalize file path: ${value}`);
|
|
6218
6074
|
}
|
|
6219
|
-
const canonical =
|
|
6075
|
+
const canonical = resolve3(canonicalAncestor, ...suffix);
|
|
6220
6076
|
if (!isWithin(this.projectRoot, canonical)) {
|
|
6221
6077
|
throw new LeaseError("INVALID_PATH", `File resolves outside the project root: ${value}`);
|
|
6222
6078
|
}
|
|
6223
6079
|
try {
|
|
6224
|
-
const target =
|
|
6080
|
+
const target = lstatSync2(canonical);
|
|
6225
6081
|
if (!target.isFile()) {
|
|
6226
6082
|
throw new LeaseError(code, `Lease targets must be regular files: ${value}`);
|
|
6227
6083
|
}
|
|
@@ -6253,6 +6109,243 @@ class FileLeaseManager {
|
|
|
6253
6109
|
}
|
|
6254
6110
|
}
|
|
6255
6111
|
|
|
6112
|
+
// src/config.ts
|
|
6113
|
+
var permissionSchema = object({
|
|
6114
|
+
action: string2().min(1),
|
|
6115
|
+
resource: string2().min(1),
|
|
6116
|
+
effect: _enum(["allow", "ask", "deny"])
|
|
6117
|
+
}).strict();
|
|
6118
|
+
var modelRefSchema = string2().min(1).regex(/^[^/#\s]+\/[^#\s]+(?:#[^#\s]+)?$/, "Expected provider/model or provider/model#variant");
|
|
6119
|
+
var agentIdSchema = string2().regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/, "Expected a filesystem-safe agent ID");
|
|
6120
|
+
var fileLeaseRoleSchema = _enum(["coordinator", "writer", "readonly"]);
|
|
6121
|
+
var agentPatchSchema = object({
|
|
6122
|
+
description: string2().min(1).optional(),
|
|
6123
|
+
mode: _enum(["primary", "subagent", "all"]).optional(),
|
|
6124
|
+
models: array(modelRefSchema).min(1).optional(),
|
|
6125
|
+
prompt: string2().min(1).optional(),
|
|
6126
|
+
skills: array(string2().min(1)).optional(),
|
|
6127
|
+
mcp: array(string2().min(1)).optional(),
|
|
6128
|
+
permissions: array(permissionSchema).optional(),
|
|
6129
|
+
fileLease: fileLeaseRoleSchema.optional(),
|
|
6130
|
+
disabled: boolean2().optional()
|
|
6131
|
+
}).strict();
|
|
6132
|
+
var leaseSchema = object({
|
|
6133
|
+
reservationTtlMinutes: number2().int().positive().max(24 * 60).optional(),
|
|
6134
|
+
activeTtlMinutes: number2().int().positive().max(24 * 60).optional()
|
|
6135
|
+
}).strict();
|
|
6136
|
+
var rootPatchSchema = object({
|
|
6137
|
+
$schema: string2().min(1).optional(),
|
|
6138
|
+
defaultAgent: agentIdSchema.optional(),
|
|
6139
|
+
agentsDirectory: string2().min(1).optional(),
|
|
6140
|
+
agents: record(agentIdSchema, agentPatchSchema).optional(),
|
|
6141
|
+
lease: leaseSchema.optional()
|
|
6142
|
+
}).strict();
|
|
6143
|
+
var resolvedAgentSchema = agentPatchSchema.extend({
|
|
6144
|
+
description: string2().min(1),
|
|
6145
|
+
mode: _enum(["primary", "subagent", "all"]),
|
|
6146
|
+
models: array(modelRefSchema).min(1),
|
|
6147
|
+
prompt: string2().min(1),
|
|
6148
|
+
skills: array(string2().min(1)),
|
|
6149
|
+
mcp: array(string2().min(1)),
|
|
6150
|
+
permissions: array(permissionSchema),
|
|
6151
|
+
fileLease: fileLeaseRoleSchema,
|
|
6152
|
+
disabled: boolean2()
|
|
6153
|
+
});
|
|
6154
|
+
var SAFE_UNTRUSTED_AGENT_FIELDS = new Set(["description"]);
|
|
6155
|
+
function assertTrustedProjectPatch(patch, sourcePath, id, trusted, knownAgents) {
|
|
6156
|
+
if (trusted)
|
|
6157
|
+
return;
|
|
6158
|
+
const fields = Object.keys(patch).filter((field) => !SAFE_UNTRUSTED_AGENT_FIELDS.has(field));
|
|
6159
|
+
if (fields.length === 0 && knownAgents.has(id))
|
|
6160
|
+
return;
|
|
6161
|
+
throw new Error(`Untrusted project config ${sourcePath} cannot override ${fields.join(", ") || `unknown agent ${id}`}. ` + `Set ${PROJECT_TRUST_ENV} to the exact token returned by computeProjectTrustToken() after reviewing these files.`);
|
|
6162
|
+
}
|
|
6163
|
+
function readJsonc2(path) {
|
|
6164
|
+
const errors = [];
|
|
6165
|
+
const value = parse2(readFileSync2(path, "utf8"), errors, {
|
|
6166
|
+
allowTrailingComma: true,
|
|
6167
|
+
disallowComments: false
|
|
6168
|
+
});
|
|
6169
|
+
if (errors.length > 0) {
|
|
6170
|
+
const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
|
|
6171
|
+
throw new Error(`Invalid JSONC in ${path}: ${details}`);
|
|
6172
|
+
}
|
|
6173
|
+
return value;
|
|
6174
|
+
}
|
|
6175
|
+
function assertWithin(base, target, label) {
|
|
6176
|
+
const child = relative3(base, target);
|
|
6177
|
+
if (child === "" || !child.startsWith("..") && !isAbsolute4(child))
|
|
6178
|
+
return;
|
|
6179
|
+
throw new Error(`${label} must stay inside ${base}: ${target}`);
|
|
6180
|
+
}
|
|
6181
|
+
function resolvePrompt(patch, sourcePath, layerDirectory) {
|
|
6182
|
+
if (!patch.prompt)
|
|
6183
|
+
return patch;
|
|
6184
|
+
const prompt = resolve4(dirname3(sourcePath), patch.prompt);
|
|
6185
|
+
assertWithin(layerDirectory, prompt, "Agent prompt");
|
|
6186
|
+
if (!existsSync3(prompt))
|
|
6187
|
+
throw new Error(`Agent prompt is missing: ${prompt}`);
|
|
6188
|
+
const promptStat = lstatSync3(prompt);
|
|
6189
|
+
if (promptStat.isSymbolicLink() || !promptStat.isFile())
|
|
6190
|
+
throw new Error(`Agent prompt must be a regular non-symlink file: ${prompt}`);
|
|
6191
|
+
const canonical = realpathSync3(prompt);
|
|
6192
|
+
assertWithin(realpathSync3(layerDirectory), canonical, "Agent prompt");
|
|
6193
|
+
const promptContent = readFileSync2(canonical, "utf8");
|
|
6194
|
+
return { ...patch, prompt: canonical, promptContent };
|
|
6195
|
+
}
|
|
6196
|
+
function loadLayer(directory, rootFileName, required, projectPolicy) {
|
|
6197
|
+
const rootPath = join3(directory, rootFileName);
|
|
6198
|
+
if (!existsSync3(rootPath)) {
|
|
6199
|
+
if (required)
|
|
6200
|
+
throw new Error(`Required config is missing: ${rootPath}`);
|
|
6201
|
+
return { agents: {}, sources: [] };
|
|
6202
|
+
}
|
|
6203
|
+
const root = rootPatchSchema.parse(readJsonc2(rootPath));
|
|
6204
|
+
if (projectPolicy && !projectPolicy.trusted) {
|
|
6205
|
+
const restricted = ["defaultAgent", "agentsDirectory", "lease"].filter((field) => Object.prototype.hasOwnProperty.call(root, field));
|
|
6206
|
+
if (restricted.length > 0)
|
|
6207
|
+
throw new Error(`Untrusted project config ${rootPath} cannot override ${restricted.join(", ")}`);
|
|
6208
|
+
}
|
|
6209
|
+
const agents = {};
|
|
6210
|
+
for (const [id, patch] of Object.entries(root.agents ?? {})) {
|
|
6211
|
+
if (projectPolicy)
|
|
6212
|
+
assertTrustedProjectPatch(patch, rootPath, id, projectPolicy.trusted, projectPolicy.knownAgents);
|
|
6213
|
+
agents[id] = resolvePrompt(patch, rootPath, directory);
|
|
6214
|
+
}
|
|
6215
|
+
const agentsDirectory = resolve4(directory, root.agentsDirectory ?? "agents");
|
|
6216
|
+
assertWithin(directory, agentsDirectory, "agentsDirectory");
|
|
6217
|
+
if (existsSync3(agentsDirectory)) {
|
|
6218
|
+
const directoryStat = lstatSync3(agentsDirectory);
|
|
6219
|
+
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
6220
|
+
throw new Error(`agentsDirectory must be a regular directory: ${agentsDirectory}`);
|
|
6221
|
+
}
|
|
6222
|
+
assertWithin(realpathSync3(directory), realpathSync3(agentsDirectory), "agentsDirectory");
|
|
6223
|
+
for (const entry of readdirSync2(agentsDirectory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6224
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonc"))
|
|
6225
|
+
continue;
|
|
6226
|
+
const id = entry.name.slice(0, -".jsonc".length);
|
|
6227
|
+
agentIdSchema.parse(id);
|
|
6228
|
+
const agentPath = join3(agentsDirectory, entry.name);
|
|
6229
|
+
const patch = agentPatchSchema.parse(readJsonc2(agentPath));
|
|
6230
|
+
if (projectPolicy)
|
|
6231
|
+
assertTrustedProjectPatch(patch, agentPath, id, projectPolicy.trusted, projectPolicy.knownAgents);
|
|
6232
|
+
agents[id] = mergeAgent(agents[id], resolvePrompt(patch, agentPath, directory));
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
return {
|
|
6236
|
+
defaultAgent: root.defaultAgent,
|
|
6237
|
+
agents,
|
|
6238
|
+
lease: root.lease,
|
|
6239
|
+
sources: [rootPath]
|
|
6240
|
+
};
|
|
6241
|
+
}
|
|
6242
|
+
function mergeAgent(base, override) {
|
|
6243
|
+
return { ...base ?? {}, ...override };
|
|
6244
|
+
}
|
|
6245
|
+
function resolveAgentConfig(patch) {
|
|
6246
|
+
const parsed = agentPatchSchema.extend({ promptContent: string2().optional() }).parse(patch);
|
|
6247
|
+
const { promptContent, ...agentPatch } = parsed;
|
|
6248
|
+
const resolved = resolvedAgentSchema.parse({
|
|
6249
|
+
skills: [],
|
|
6250
|
+
mcp: [],
|
|
6251
|
+
permissions: [],
|
|
6252
|
+
fileLease: "readonly",
|
|
6253
|
+
disabled: false,
|
|
6254
|
+
...agentPatch
|
|
6255
|
+
});
|
|
6256
|
+
return promptContent === undefined ? resolved : { ...resolved, promptContent };
|
|
6257
|
+
}
|
|
6258
|
+
function findPackageRoot() {
|
|
6259
|
+
let current = dirname3(fileURLToPath(import.meta.url));
|
|
6260
|
+
while (true) {
|
|
6261
|
+
if (existsSync3(join3(current, "defaults", "default.jsonc")))
|
|
6262
|
+
return current;
|
|
6263
|
+
const parent = dirname3(current);
|
|
6264
|
+
if (parent === current)
|
|
6265
|
+
break;
|
|
6266
|
+
current = parent;
|
|
6267
|
+
}
|
|
6268
|
+
throw new Error("Unable to locate agent-gvozd package defaults");
|
|
6269
|
+
}
|
|
6270
|
+
function findProjectRoot(start) {
|
|
6271
|
+
let current = resolve4(start);
|
|
6272
|
+
while (true) {
|
|
6273
|
+
const git = join3(current, ".git");
|
|
6274
|
+
if (existsSync3(git))
|
|
6275
|
+
return current;
|
|
6276
|
+
const parent = dirname3(current);
|
|
6277
|
+
if (parent === current)
|
|
6278
|
+
return resolve4(start);
|
|
6279
|
+
current = parent;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
function loadConfig(projectDirectory, options = {}) {
|
|
6283
|
+
const projectRoot = findProjectRoot(projectDirectory);
|
|
6284
|
+
const packageRoot = findPackageRoot();
|
|
6285
|
+
const projectConfigDirectory = join3(projectRoot, "docs", ".gvozd");
|
|
6286
|
+
const globalConfigDirectory = join3(resolveOpenCodeConfigRoot(options.env, options.platform, options.home, options.configRoot), "gvozd");
|
|
6287
|
+
const baseLayers = [
|
|
6288
|
+
loadLayer(join3(packageRoot, "defaults"), "default.jsonc", true),
|
|
6289
|
+
loadLayer(globalConfigDirectory, "config.jsonc", false)
|
|
6290
|
+
];
|
|
6291
|
+
const knownAgents = new Set(baseLayers.flatMap((layer) => Object.keys(layer.agents)));
|
|
6292
|
+
const includeProject = options.includeProject !== false;
|
|
6293
|
+
const suppliedToken = includeProject ? options.projectTrustToken ?? (options.env ?? process.env)[PROJECT_TRUST_ENV] : undefined;
|
|
6294
|
+
const trustProjectConfig = includeProject && suppliedToken !== undefined && suppliedToken === computeProjectTrustToken(projectRoot);
|
|
6295
|
+
const projectLayer = includeProject ? loadLayer(projectConfigDirectory, "config.jsonc", false, { trusted: trustProjectConfig, knownAgents }) : undefined;
|
|
6296
|
+
if (trustProjectConfig && suppliedToken !== computeProjectTrustToken(projectRoot)) {
|
|
6297
|
+
throw new Error("Project configuration changed while its trust token was being validated; review it and compute a new token");
|
|
6298
|
+
}
|
|
6299
|
+
const layers = [...baseLayers, ...projectLayer ? [projectLayer] : []];
|
|
6300
|
+
const lease = {
|
|
6301
|
+
reservationTtlMs: DEFAULT_RESERVATION_TTL_MS,
|
|
6302
|
+
activeTtlMs: DEFAULT_ACTIVE_TTL_MS
|
|
6303
|
+
};
|
|
6304
|
+
for (const layer of layers) {
|
|
6305
|
+
if (!layer.lease)
|
|
6306
|
+
continue;
|
|
6307
|
+
if (layer.lease.reservationTtlMinutes !== undefined) {
|
|
6308
|
+
lease.reservationTtlMs = layer.lease.reservationTtlMinutes * 60000;
|
|
6309
|
+
}
|
|
6310
|
+
if (layer.lease.activeTtlMinutes !== undefined) {
|
|
6311
|
+
lease.activeTtlMs = layer.lease.activeTtlMinutes * 60000;
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
let defaultAgent;
|
|
6315
|
+
const agents = {};
|
|
6316
|
+
for (const layer of layers) {
|
|
6317
|
+
defaultAgent = layer.defaultAgent ?? defaultAgent;
|
|
6318
|
+
for (const [id, patch] of Object.entries(layer.agents)) {
|
|
6319
|
+
agents[id] = mergeAgent(agents[id], patch);
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
if (!defaultAgent)
|
|
6323
|
+
throw new Error("defaultAgent is not configured");
|
|
6324
|
+
const resolvedAgents = Object.fromEntries(Object.entries(agents).map(([id, patch]) => [
|
|
6325
|
+
id,
|
|
6326
|
+
(() => {
|
|
6327
|
+
const agent = resolveAgentConfig(patch);
|
|
6328
|
+
if (agent.promptContent === undefined)
|
|
6329
|
+
throw new Error(`Agent prompt snapshot is missing after configuration load: ${agent.prompt}`);
|
|
6330
|
+
return agent;
|
|
6331
|
+
})()
|
|
6332
|
+
]));
|
|
6333
|
+
const defaultConfig = resolvedAgents[defaultAgent];
|
|
6334
|
+
if (!defaultConfig || defaultConfig.disabled || defaultConfig.mode === "subagent") {
|
|
6335
|
+
throw new Error(`defaultAgent must reference an enabled primary agent: ${defaultAgent}`);
|
|
6336
|
+
}
|
|
6337
|
+
return {
|
|
6338
|
+
defaultAgent,
|
|
6339
|
+
agents: resolvedAgents,
|
|
6340
|
+
lease,
|
|
6341
|
+
packageRoot,
|
|
6342
|
+
projectRoot,
|
|
6343
|
+
projectConfigDirectory,
|
|
6344
|
+
globalConfigDirectory,
|
|
6345
|
+
sources: layers.flatMap((layer) => layer.sources)
|
|
6346
|
+
};
|
|
6347
|
+
}
|
|
6348
|
+
|
|
6256
6349
|
// src/file-lease-plugin.ts
|
|
6257
6350
|
var GVOZD_LEASE_TOOL = "gvozd_lease";
|
|
6258
6351
|
var GVOZD_CLAIM_TOOL = "gvozd_claim";
|
|
@@ -6263,26 +6356,12 @@ var TERMINAL_SESSION_EVENTS = new Set([
|
|
|
6263
6356
|
"session.idle",
|
|
6264
6357
|
"session.deleted"
|
|
6265
6358
|
]);
|
|
6266
|
-
var SAFE_SHELL_DURING_ACTIVE_LEASES = new Set([
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
"git rev-parse --git-dir",
|
|
6273
|
-
"git remote -v",
|
|
6274
|
-
"git branch --show-current",
|
|
6275
|
-
"git stash list",
|
|
6276
|
-
"GIT_OPTIONAL_LOCKS=0 git status",
|
|
6277
|
-
"GIT_OPTIONAL_LOCKS=0 git status --short",
|
|
6278
|
-
"GIT_OPTIONAL_LOCKS=0 git status --short --branch",
|
|
6279
|
-
"GIT_OPTIONAL_LOCKS=0 git status --porcelain=v1 --branch",
|
|
6280
|
-
"GIT_OPTIONAL_LOCKS=0 git rev-parse --show-toplevel",
|
|
6281
|
-
"GIT_OPTIONAL_LOCKS=0 git rev-parse --git-dir",
|
|
6282
|
-
"GIT_OPTIONAL_LOCKS=0 git remote -v",
|
|
6283
|
-
"GIT_OPTIONAL_LOCKS=0 git branch --show-current",
|
|
6284
|
-
"GIT_OPTIONAL_LOCKS=0 git stash list"
|
|
6285
|
-
]);
|
|
6359
|
+
var SAFE_SHELL_DURING_ACTIVE_LEASES = new Set([...INSPECTION_COMMANDS, ...TOOLCHAIN_COMMANDS, ...GIT_READONLY_COMMANDS].flatMap(({ exact, wildcard }) => [
|
|
6360
|
+
exact,
|
|
6361
|
+
wildcard,
|
|
6362
|
+
...GIT_ENV_PREFIXES.map((prefix) => `${prefix} ${exact}`),
|
|
6363
|
+
...GIT_ENV_PREFIXES.map((prefix) => `${prefix} ${wildcard}`)
|
|
6364
|
+
]));
|
|
6286
6365
|
var leaseInputSchema = discriminatedUnion("operation", [
|
|
6287
6366
|
object({
|
|
6288
6367
|
operation: literal("reserve"),
|
|
@@ -6332,8 +6411,8 @@ function deny(event, message) {
|
|
|
6332
6411
|
event.message = message;
|
|
6333
6412
|
return true;
|
|
6334
6413
|
}
|
|
6335
|
-
function
|
|
6336
|
-
return agent
|
|
6414
|
+
function safeReadonlyShell(agent, resources) {
|
|
6415
|
+
return agent !== undefined && resources.length > 0 && resources.every((resource) => SAFE_SHELL_DURING_ACTIVE_LEASES.has(resource));
|
|
6337
6416
|
}
|
|
6338
6417
|
function enforceFileLeasePermission(event, config, manager) {
|
|
6339
6418
|
const role = roleOf(config, event.agent);
|
|
@@ -6356,7 +6435,7 @@ function enforceFileLeasePermission(event, config, manager) {
|
|
|
6356
6435
|
if (role === "coordinator" || role === "writer") {
|
|
6357
6436
|
return deny(event, `Agent ${event.agent} cannot use shell while file leases enforce structured mutations`);
|
|
6358
6437
|
}
|
|
6359
|
-
if (manager.hasActiveLeases() && !(role === "readonly" &&
|
|
6438
|
+
if (manager.hasActiveLeases() && !(role === "readonly" && safeReadonlyShell(event.agent, event.resources))) {
|
|
6360
6439
|
return deny(event, "Shell commands are paused until all active writer file leases are released");
|
|
6361
6440
|
}
|
|
6362
6441
|
return false;
|
|
@@ -6382,7 +6461,12 @@ function requireRole(config, agentID, allowed) {
|
|
|
6382
6461
|
}
|
|
6383
6462
|
async function installFileLeaseRuntime(ctx, config, options = {}) {
|
|
6384
6463
|
const caseInsensitive = options.caseInsensitive ?? resolveCaseInsensitiveFilesystem(options.env ?? process.env, options.platform ?? process.platform);
|
|
6385
|
-
const manager = new FileLeaseManager({
|
|
6464
|
+
const manager = new FileLeaseManager({
|
|
6465
|
+
projectRoot: config.projectRoot,
|
|
6466
|
+
caseInsensitive,
|
|
6467
|
+
reservationTtlMs: config.lease.reservationTtlMs,
|
|
6468
|
+
activeTtlMs: config.lease.activeTtlMs
|
|
6469
|
+
});
|
|
6386
6470
|
const toolTransform = await ctx.tool.transform((tools) => {
|
|
6387
6471
|
tools.namespace({
|
|
6388
6472
|
name: "gvozd",
|