@kontextmind/kxm 0.7.168 → 0.7.169
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +12 -0
- package/docs/reference/config-reference.md +24 -12
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +119 -3
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +127 -5
- package/plugins/kxm/dist/runtime.js +127 -5
- package/plugins/kxm/dist/server.js +7 -0
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +3 -2
- package/plugins/kxm/src/engine.ts +105 -8
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/project-config.ts +63 -1
package/CHANGELOG.md
CHANGED
|
@@ -181,6 +181,18 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
181
181
|
|
|
182
182
|
### Changed
|
|
183
183
|
|
|
184
|
+
- **Tool policy is enforced at load and before dispatch.**
|
|
185
|
+
`coordinator` and `read-only` select the read-only one-shot profile.
|
|
186
|
+
`workspace-writer` and `tests-writer` select the edit profile. Dispatch
|
|
187
|
+
uses the narrowest of the role `permission`, that preset profile, and the
|
|
188
|
+
step's repository access. An agent preset wider than its role is
|
|
189
|
+
`tool_preset_exceeds_role` at load, and `step_unsupported` before a
|
|
190
|
+
producer starts if those files were already loaded. A write step whose
|
|
191
|
+
resolved profile is read-only is the same handoff. The detail names the
|
|
192
|
+
role permission and the step access. `tools.allow` and `tools.deny` stay
|
|
193
|
+
on the MCP surface through `enforceToolPolicy`. See
|
|
194
|
+
[agent files](docs/reference/config-reference.md#kxmagentsidyaml-kxmagentv1).
|
|
195
|
+
|
|
184
196
|
- **The checked-in price catalog and model inventory match the live OpenRouter and Nous feeds.**
|
|
185
197
|
`.kxm/prices.yaml` is dated 2026-09-28. It adds `xai/grok-4.7` (alias
|
|
186
198
|
`x-ai/grok-4.7`) at $1.60 input and $4.80 output per 1M tokens, with cache
|
|
@@ -337,7 +337,7 @@ with `retired_agent_routing_fields`.
|
|
|
337
337
|
| `skills` | Unique identifiers, at most 64 | Optional | Skill names granted to the agent |
|
|
338
338
|
| `instructions` | String, at most 16,000 characters | Optional | Not read by any code path yet. Step `instructions` are what reach the prompt. |
|
|
339
339
|
| `executor` | `local`, `ssh`, or `exe-dev` | Optional | Loader (`executor_unknown`); recorded in the executor-policy revision; no dispatch path selects an executor from it yet |
|
|
340
|
-
| `tools.preset` | `coordinator`, `read-only`, `workspace-writer`, or `tests-writer` | Optional | Loader (`tool_preset_unknown`): the preset must be registered.
|
|
340
|
+
| `tools.preset` | `coordinator`, `read-only`, `workspace-writer`, or `tests-writer` | Optional | Loader (`tool_preset_unknown`): the preset must be registered. `coordinator` and `read-only` select the read-only one-shot profile. `workspace-writer` and `tests-writer` select the edit profile. An agent preset may only narrow the role. A wider preset is `tool_preset_exceeds_role` at load. |
|
|
341
341
|
| `tools.allow`, `tools.deny` | Unique identifiers, at most 128 each | Optional | A tool in both lists is `tool_policy_contradiction`; steps may only narrow the ceiling |
|
|
342
342
|
| `defaultRepositoryAccess` | `none`, `read`, or `write` | Optional; the ceiling is `none` when absent | Loader: access ceiling for repositories not listed in `repositories` |
|
|
343
343
|
| `repositories` | Map of repository ID to `none`, `read`, or `write`; at most 64 | Optional | Loader: per-repository access ceiling; IDs must be declared (`repository_unknown`) |
|
|
@@ -349,18 +349,30 @@ with `retired_agent_routing_fields`.
|
|
|
349
349
|
| `session.reuse` | `compatible-run-scope` | Optional | Not read by any code path yet |
|
|
350
350
|
| `session.maxIdleMs` | Integer, 0 to 31,536,000,000 | Optional | Not read by any code path yet |
|
|
351
351
|
|
|
352
|
-
Tool presets are names checked against a registered list
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
`
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
352
|
+
Tool presets are names checked against a registered list, then mapped onto
|
|
353
|
+
the harness one-shot profiles in `plugins/kxm/src/harness.ts`. `coordinator`
|
|
354
|
+
and `read-only` use the read-only argument set (`READ_ONLY_ONESHOT_ARGS`).
|
|
355
|
+
`workspace-writer` and `tests-writer` use the audited writer set
|
|
356
|
+
(`WRITER_ONESHOT_ARGS`), which exists only for `pi` (`-a` with extensions,
|
|
357
|
+
skills, prompt templates and sessions off) and `grok` (`--always-approve`
|
|
358
|
+
with subagents and web search off). Both approve every tool call, shell
|
|
359
|
+
commands included, and neither confines the process to the checkout.
|
|
360
|
+
|
|
361
|
+
Dispatch permission is the narrowest of the role `permission`, the preset
|
|
362
|
+
profile (the role preset, then the agent preset), and the step's repository
|
|
363
|
+
access (`write` is edit; `read` and `none` are read-only). A preset cannot
|
|
364
|
+
widen a role. An agent preset that is wider fails load with
|
|
365
|
+
`tool_preset_exceeds_role`, and dispatch refuses that file again with
|
|
366
|
+
`step_unsupported` (field `tools.preset`) when the process already loaded
|
|
367
|
+
it. A write step whose resolved profile is read-only is `step_unsupported`
|
|
368
|
+
(field `permission`) before the producer starts. The detail names the role
|
|
369
|
+
permission and the step access. A live write that does resolve to edit
|
|
370
|
+
still hands off on a harness with no audited writer set; see
|
|
362
371
|
[Steps the Runtime does not execute yet](#steps-the-runtime-does-not-execute-yet).
|
|
363
372
|
|
|
373
|
+
`tools.allow` and `tools.deny` apply to the MCP surface through
|
|
374
|
+
`enforceToolPolicy` in `plugins/kxm/src/commands.ts`.
|
|
375
|
+
|
|
364
376
|
Example (the shape `kxm init` writes for `writer`):
|
|
365
377
|
|
|
366
378
|
```yaml
|
|
@@ -394,7 +406,7 @@ Runtime cannot reach the `antigravity` Pi provider yet. `kxm run` and the
|
|
|
394
406
|
Runtime read the agents. `kxm trust` diffs them.
|
|
395
407
|
|
|
396
408
|
Error codes: `retired_agent_routing_fields`, `executor_unknown`,
|
|
397
|
-
`tool_preset_unknown`, `tool_policy_contradiction`, `repository_unknown`, and
|
|
409
|
+
`tool_preset_unknown`, `tool_preset_exceeds_role`, `tool_policy_contradiction`, `repository_unknown`, and
|
|
398
410
|
the path codes under
|
|
399
411
|
[Rules shared by the project bundle](#rules-shared-by-the-project-bundle).
|
|
400
412
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.169",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -20122,6 +20122,21 @@ import { pathToFileURL } from "node:url";
|
|
|
20122
20122
|
function defaultKxmSchemaDir() {
|
|
20123
20123
|
return join9(findKxmRepoRoot(import.meta.url), "schemas");
|
|
20124
20124
|
}
|
|
20125
|
+
function toolPresetProfile(preset) {
|
|
20126
|
+
if (!Object.hasOwn(TOOL_PRESET_PROFILES, preset)) return void 0;
|
|
20127
|
+
return TOOL_PRESET_PROFILES[preset];
|
|
20128
|
+
}
|
|
20129
|
+
function toolPresetExceedsRoleMessage(preset, roleId, rolePermission, rolePreset) {
|
|
20130
|
+
const profile = toolPresetProfile(preset);
|
|
20131
|
+
if (profile !== "edit") return void 0;
|
|
20132
|
+
if (rolePermission === "read-only") {
|
|
20133
|
+
return `tools.preset ${preset} exceeds role ${roleId} permission read-only`;
|
|
20134
|
+
}
|
|
20135
|
+
if (rolePreset !== void 0 && toolPresetProfile(rolePreset) === "read-only") {
|
|
20136
|
+
return `tools.preset ${preset} exceeds role ${roleId} preset ${rolePreset}`;
|
|
20137
|
+
}
|
|
20138
|
+
return void 0;
|
|
20139
|
+
}
|
|
20125
20140
|
function compareCodeUnits4(left, right) {
|
|
20126
20141
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
20127
20142
|
}
|
|
@@ -20407,6 +20422,15 @@ function objectValue(value) {
|
|
|
20407
20422
|
function stringValue(value) {
|
|
20408
20423
|
return typeof value === "string" ? value : void 0;
|
|
20409
20424
|
}
|
|
20425
|
+
function roleById(roles, roleId) {
|
|
20426
|
+
const direct = roles.get(roleId);
|
|
20427
|
+
if (direct) return direct;
|
|
20428
|
+
const folded = roleId.toLocaleLowerCase("en-US");
|
|
20429
|
+
for (const role of roles.values()) {
|
|
20430
|
+
if ((role.id ?? "").toLocaleLowerCase("en-US") === folded) return role;
|
|
20431
|
+
}
|
|
20432
|
+
return void 0;
|
|
20433
|
+
}
|
|
20410
20434
|
function numberValue(value, fallback) {
|
|
20411
20435
|
return typeof value === "number" ? value : fallback;
|
|
20412
20436
|
}
|
|
@@ -20917,10 +20941,29 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
20917
20941
|
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
20918
20942
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
20919
20943
|
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
20944
|
+
const roleId = stringValue(agent.value.role);
|
|
20945
|
+
const role = roleId ? roleById(roles, roleId) : void 0;
|
|
20946
|
+
const rolePermission = stringValue(role?.value.permission);
|
|
20947
|
+
if (preset && role && (rolePermission === "edit" || rolePermission === "read-only")) {
|
|
20948
|
+
const message = toolPresetExceedsRoleMessage(
|
|
20949
|
+
preset,
|
|
20950
|
+
role.id ?? roleId ?? "role",
|
|
20951
|
+
rolePermission,
|
|
20952
|
+
stringValue(objectValue(role.value.tools)?.preset)
|
|
20953
|
+
);
|
|
20954
|
+
if (message) issues.push(issue3("semantic", "tool_preset_exceeds_role", agent.logicalPath, message));
|
|
20955
|
+
}
|
|
20920
20956
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
20921
20957
|
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
20922
20958
|
}
|
|
20923
20959
|
}
|
|
20960
|
+
for (const role of roles.values()) {
|
|
20961
|
+
const permission = stringValue(role.value.permission);
|
|
20962
|
+
const rolePreset = stringValue(objectValue(role.value.tools)?.preset);
|
|
20963
|
+
if (permission === "read-only" && rolePreset && toolPresetProfile(rolePreset) === "edit") {
|
|
20964
|
+
issues.push(issue3("semantic", "tool_preset_exceeds_role", role.logicalPath, `tools.preset ${rolePreset} exceeds permission read-only`));
|
|
20965
|
+
}
|
|
20966
|
+
}
|
|
20924
20967
|
validateModelReferences(models, issues);
|
|
20925
20968
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
20926
20969
|
if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
@@ -21398,7 +21441,7 @@ function planKxmInitialization(start = process.cwd(), options = {}) {
|
|
|
21398
21441
|
}
|
|
21399
21442
|
return { mode: "create", inspectedFrom, projectRoot: candidateRoot, changesRequired: true, issues: [], legacyInputs: [] };
|
|
21400
21443
|
}
|
|
21401
|
-
var import__, KxmConfigError, DEFAULT_SCHEMA_DIR, RESOURCE_SCHEMA, IDENTIFIER2, WINDOWS_RESERVED, BUILTIN_EXECUTORS, BUILTIN_TOOL_PRESETS, SECRET_VALUE_PATTERNS, KxmSchemaRegistry, cachedRunEventRegistry, GATE_STEP_OUTCOMES, LEGACY_CONFIG_FILES;
|
|
21444
|
+
var import__, KxmConfigError, DEFAULT_SCHEMA_DIR, RESOURCE_SCHEMA, IDENTIFIER2, WINDOWS_RESERVED, BUILTIN_EXECUTORS, TOOL_PRESET_PROFILES, BUILTIN_TOOL_PRESETS, SECRET_VALUE_PATTERNS, KxmSchemaRegistry, cachedRunEventRegistry, GATE_STEP_OUTCOMES, LEGACY_CONFIG_FILES;
|
|
21402
21445
|
var init_project_config = __esm({
|
|
21403
21446
|
"plugins/kxm/src/project-config.ts"() {
|
|
21404
21447
|
"use strict";
|
|
@@ -21432,7 +21475,13 @@ var init_project_config = __esm({
|
|
|
21432
21475
|
IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
21433
21476
|
WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
|
|
21434
21477
|
BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
|
|
21435
|
-
|
|
21478
|
+
TOOL_PRESET_PROFILES = Object.freeze({
|
|
21479
|
+
coordinator: "read-only",
|
|
21480
|
+
"read-only": "read-only",
|
|
21481
|
+
"workspace-writer": "edit",
|
|
21482
|
+
"tests-writer": "edit"
|
|
21483
|
+
});
|
|
21484
|
+
BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
|
|
21436
21485
|
SECRET_VALUE_PATTERNS = [
|
|
21437
21486
|
/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
|
|
21438
21487
|
/\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b/,
|
|
@@ -27709,6 +27758,68 @@ function unsupportedAgentStepTimeout(step, limitMs) {
|
|
|
27709
27758
|
detail: "step timeoutMs is wider than project limits.agentStepTimeoutMs"
|
|
27710
27759
|
};
|
|
27711
27760
|
}
|
|
27761
|
+
function narrowerPermission(left, right) {
|
|
27762
|
+
return left === "read-only" || right === "read-only" ? "read-only" : "edit";
|
|
27763
|
+
}
|
|
27764
|
+
function stepRepositoryPermission(step) {
|
|
27765
|
+
return Object.values(step.repositories).some((access) => access === "write") ? "edit" : "read-only";
|
|
27766
|
+
}
|
|
27767
|
+
function stepAccessDetail(step) {
|
|
27768
|
+
const entries = Object.entries(step.repositories);
|
|
27769
|
+
if (entries.length === 0) return "none";
|
|
27770
|
+
return entries.map(([id, access]) => `${id} ${access}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0).join(", ");
|
|
27771
|
+
}
|
|
27772
|
+
function yamlToolsPreset(doc) {
|
|
27773
|
+
const tools = doc?.tools;
|
|
27774
|
+
if (!tools || typeof tools !== "object" || Array.isArray(tools)) return void 0;
|
|
27775
|
+
const preset = tools.preset;
|
|
27776
|
+
return typeof preset === "string" && preset.length > 0 ? preset : void 0;
|
|
27777
|
+
}
|
|
27778
|
+
function resolveToolDispatch(projectRoot, step, agentId) {
|
|
27779
|
+
const stepPermission = stepRepositoryPermission(step);
|
|
27780
|
+
if (step.kind !== "agent" && step.kind !== "moa") return { permission: stepPermission };
|
|
27781
|
+
const agentFile = findYamlBasename(join19(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
27782
|
+
const agent = readYamlFile(join19(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
27783
|
+
const roleId = typeof agent?.role === "string" ? agent.role : "";
|
|
27784
|
+
if (!roleId) return { permission: stepPermission };
|
|
27785
|
+
const roleDoc = readYamlFile(join19(projectRoot, ".kxm", "roles", `${roleId}.yaml`));
|
|
27786
|
+
const rawPermission = roleDoc?.permission;
|
|
27787
|
+
const rolePermission = rawPermission === "edit" || rawPermission === "read-only" ? rawPermission : void 0;
|
|
27788
|
+
if (!rolePermission) return { permission: stepPermission };
|
|
27789
|
+
const rolePreset = yamlToolsPreset(roleDoc);
|
|
27790
|
+
const agentPreset = yamlToolsPreset(agent);
|
|
27791
|
+
const rolePresetProfile = rolePreset ? toolPresetProfile(rolePreset) : void 0;
|
|
27792
|
+
if (rolePreset && rolePresetProfile === "edit" && rolePermission === "read-only") {
|
|
27793
|
+
return {
|
|
27794
|
+
error: {
|
|
27795
|
+
reason: "step_unsupported",
|
|
27796
|
+
field: "tools.preset",
|
|
27797
|
+
detail: `tools.preset ${rolePreset} exceeds role ${roleId} permission read-only`
|
|
27798
|
+
}
|
|
27799
|
+
};
|
|
27800
|
+
}
|
|
27801
|
+
if (agentPreset) {
|
|
27802
|
+
const exceeds = toolPresetExceedsRoleMessage(agentPreset, roleId, rolePermission, rolePreset);
|
|
27803
|
+
if (exceeds) {
|
|
27804
|
+
return { error: { reason: "step_unsupported", field: "tools.preset", detail: exceeds } };
|
|
27805
|
+
}
|
|
27806
|
+
}
|
|
27807
|
+
let chain = rolePermission;
|
|
27808
|
+
if (rolePresetProfile) chain = narrowerPermission(chain, rolePresetProfile);
|
|
27809
|
+
const agentProfile = agentPreset ? toolPresetProfile(agentPreset) : void 0;
|
|
27810
|
+
if (agentProfile) chain = narrowerPermission(chain, agentProfile);
|
|
27811
|
+
const permission = narrowerPermission(chain, stepPermission);
|
|
27812
|
+
if (stepPermission === "edit" && permission === "read-only") {
|
|
27813
|
+
return {
|
|
27814
|
+
error: {
|
|
27815
|
+
reason: "step_unsupported",
|
|
27816
|
+
field: "permission",
|
|
27817
|
+
detail: `role permission ${rolePermission} resolves to ${chain}; step access ${stepAccessDetail(step)}`
|
|
27818
|
+
}
|
|
27819
|
+
};
|
|
27820
|
+
}
|
|
27821
|
+
return { permission };
|
|
27822
|
+
}
|
|
27712
27823
|
function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
|
|
27713
27824
|
const workflow = lookupKxmResource(bundle.workflows.values(), workflowId, "workflow");
|
|
27714
27825
|
if (!workflow) throw runtimeError("run_workflow_unknown", workflowId, `workflow ${workflowId} does not exist in this project`);
|
|
@@ -27736,6 +27847,11 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
|
|
|
27736
27847
|
if (step.kind !== "agent" && step.kind !== "moa") continue;
|
|
27737
27848
|
const agentIds = step.assignments.allowedAgents.length > 0 ? step.assignments.allowedAgents : [step.agent];
|
|
27738
27849
|
for (const agentId of agentIds) {
|
|
27850
|
+
const toolDispatch = resolveToolDispatch(projectRoot, step, agentId);
|
|
27851
|
+
if ("error" in toolDispatch) {
|
|
27852
|
+
prerequisites.push({ ...toolDispatch.error, stepId });
|
|
27853
|
+
continue;
|
|
27854
|
+
}
|
|
27739
27855
|
const route = resolveProducerRoute(projectRoot, step, agentId);
|
|
27740
27856
|
if ("error" in route) {
|
|
27741
27857
|
const detail = route.error.detail === "producer_route_unsupported: agent step model is not honored" ? `${agentId}: agent step model is not honored; remove model from the step` : `${agentId}: ${route.error.detail}; set role in .kxm/agents/${agentId}.yaml and admit the route model with kxm routes admit --model <provider/model>`;
|
|
@@ -27743,7 +27859,7 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
|
|
|
27743
27859
|
continue;
|
|
27744
27860
|
}
|
|
27745
27861
|
const harness = route.harness;
|
|
27746
|
-
const permission =
|
|
27862
|
+
const permission = toolDispatch.permission;
|
|
27747
27863
|
if (!BUILTIN_HARNESSES.some((entry) => entry.id === harness && entry.oneShot) || !oneShotPermissionArgs(harness, permission)) {
|
|
27748
27864
|
prerequisites.push({ reason: "step_unsupported", stepId, field: "harness", detail: `${agentId}: ${harness} has no audited ${permission} one-shot profile; use a supported workflow or execute this work directly in ${harness}, without substituting another harness` });
|
|
27749
27865
|
continue;
|
|
@@ -25438,7 +25438,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25438
25438
|
}
|
|
25439
25439
|
|
|
25440
25440
|
// plugins/kxm/src/mcp-server.ts
|
|
25441
|
-
var VERSION = "0.7.
|
|
25441
|
+
var VERSION = "0.7.169";
|
|
25442
25442
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25443
25443
|
var inbox = /* @__PURE__ */ new Map();
|
|
25444
25444
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
@@ -17261,7 +17261,28 @@ var RESOURCE_SCHEMA = Object.freeze({
|
|
|
17261
17261
|
var IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
17262
17262
|
var WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
|
|
17263
17263
|
var BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
|
|
17264
|
-
var
|
|
17264
|
+
var TOOL_PRESET_PROFILES = Object.freeze({
|
|
17265
|
+
coordinator: "read-only",
|
|
17266
|
+
"read-only": "read-only",
|
|
17267
|
+
"workspace-writer": "edit",
|
|
17268
|
+
"tests-writer": "edit"
|
|
17269
|
+
});
|
|
17270
|
+
var BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
|
|
17271
|
+
function toolPresetProfile(preset) {
|
|
17272
|
+
if (!Object.hasOwn(TOOL_PRESET_PROFILES, preset)) return void 0;
|
|
17273
|
+
return TOOL_PRESET_PROFILES[preset];
|
|
17274
|
+
}
|
|
17275
|
+
function toolPresetExceedsRoleMessage(preset, roleId, rolePermission, rolePreset) {
|
|
17276
|
+
const profile = toolPresetProfile(preset);
|
|
17277
|
+
if (profile !== "edit") return void 0;
|
|
17278
|
+
if (rolePermission === "read-only") {
|
|
17279
|
+
return `tools.preset ${preset} exceeds role ${roleId} permission read-only`;
|
|
17280
|
+
}
|
|
17281
|
+
if (rolePreset !== void 0 && toolPresetProfile(rolePreset) === "read-only") {
|
|
17282
|
+
return `tools.preset ${preset} exceeds role ${roleId} preset ${rolePreset}`;
|
|
17283
|
+
}
|
|
17284
|
+
return void 0;
|
|
17285
|
+
}
|
|
17265
17286
|
var SECRET_VALUE_PATTERNS = [
|
|
17266
17287
|
/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
|
|
17267
17288
|
/\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b/,
|
|
@@ -17661,6 +17682,15 @@ function objectValue(value) {
|
|
|
17661
17682
|
function stringValue(value) {
|
|
17662
17683
|
return typeof value === "string" ? value : void 0;
|
|
17663
17684
|
}
|
|
17685
|
+
function roleById(roles, roleId) {
|
|
17686
|
+
const direct = roles.get(roleId);
|
|
17687
|
+
if (direct) return direct;
|
|
17688
|
+
const folded = roleId.toLocaleLowerCase("en-US");
|
|
17689
|
+
for (const role of roles.values()) {
|
|
17690
|
+
if ((role.id ?? "").toLocaleLowerCase("en-US") === folded) return role;
|
|
17691
|
+
}
|
|
17692
|
+
return void 0;
|
|
17693
|
+
}
|
|
17664
17694
|
function numberValue(value, fallback) {
|
|
17665
17695
|
return typeof value === "number" ? value : fallback;
|
|
17666
17696
|
}
|
|
@@ -18175,10 +18205,29 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
18175
18205
|
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
18176
18206
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
18177
18207
|
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
18208
|
+
const roleId = stringValue(agent.value.role);
|
|
18209
|
+
const role = roleId ? roleById(roles, roleId) : void 0;
|
|
18210
|
+
const rolePermission = stringValue(role?.value.permission);
|
|
18211
|
+
if (preset && role && (rolePermission === "edit" || rolePermission === "read-only")) {
|
|
18212
|
+
const message = toolPresetExceedsRoleMessage(
|
|
18213
|
+
preset,
|
|
18214
|
+
role.id ?? roleId ?? "role",
|
|
18215
|
+
rolePermission,
|
|
18216
|
+
stringValue(objectValue(role.value.tools)?.preset)
|
|
18217
|
+
);
|
|
18218
|
+
if (message) issues.push(issue3("semantic", "tool_preset_exceeds_role", agent.logicalPath, message));
|
|
18219
|
+
}
|
|
18178
18220
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
18179
18221
|
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
18180
18222
|
}
|
|
18181
18223
|
}
|
|
18224
|
+
for (const role of roles.values()) {
|
|
18225
|
+
const permission = stringValue(role.value.permission);
|
|
18226
|
+
const rolePreset = stringValue(objectValue(role.value.tools)?.preset);
|
|
18227
|
+
if (permission === "read-only" && rolePreset && toolPresetProfile(rolePreset) === "edit") {
|
|
18228
|
+
issues.push(issue3("semantic", "tool_preset_exceeds_role", role.logicalPath, `tools.preset ${rolePreset} exceeds permission read-only`));
|
|
18229
|
+
}
|
|
18230
|
+
}
|
|
18182
18231
|
validateModelReferences(models, issues);
|
|
18183
18232
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
18184
18233
|
if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
@@ -29377,6 +29426,68 @@ function unsupportedAgentStepTimeout(step, limitMs) {
|
|
|
29377
29426
|
detail: "step timeoutMs is wider than project limits.agentStepTimeoutMs"
|
|
29378
29427
|
};
|
|
29379
29428
|
}
|
|
29429
|
+
function narrowerPermission(left, right) {
|
|
29430
|
+
return left === "read-only" || right === "read-only" ? "read-only" : "edit";
|
|
29431
|
+
}
|
|
29432
|
+
function stepRepositoryPermission(step) {
|
|
29433
|
+
return Object.values(step.repositories).some((access) => access === "write") ? "edit" : "read-only";
|
|
29434
|
+
}
|
|
29435
|
+
function stepAccessDetail(step) {
|
|
29436
|
+
const entries = Object.entries(step.repositories);
|
|
29437
|
+
if (entries.length === 0) return "none";
|
|
29438
|
+
return entries.map(([id, access]) => `${id} ${access}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0).join(", ");
|
|
29439
|
+
}
|
|
29440
|
+
function yamlToolsPreset(doc) {
|
|
29441
|
+
const tools = doc?.tools;
|
|
29442
|
+
if (!tools || typeof tools !== "object" || Array.isArray(tools)) return void 0;
|
|
29443
|
+
const preset = tools.preset;
|
|
29444
|
+
return typeof preset === "string" && preset.length > 0 ? preset : void 0;
|
|
29445
|
+
}
|
|
29446
|
+
function resolveToolDispatch(projectRoot, step, agentId) {
|
|
29447
|
+
const stepPermission = stepRepositoryPermission(step);
|
|
29448
|
+
if (step.kind !== "agent" && step.kind !== "moa") return { permission: stepPermission };
|
|
29449
|
+
const agentFile = findYamlBasename(join18(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
29450
|
+
const agent = readYamlFile(join18(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
29451
|
+
const roleId = typeof agent?.role === "string" ? agent.role : "";
|
|
29452
|
+
if (!roleId) return { permission: stepPermission };
|
|
29453
|
+
const roleDoc = readYamlFile(join18(projectRoot, ".kxm", "roles", `${roleId}.yaml`));
|
|
29454
|
+
const rawPermission = roleDoc?.permission;
|
|
29455
|
+
const rolePermission = rawPermission === "edit" || rawPermission === "read-only" ? rawPermission : void 0;
|
|
29456
|
+
if (!rolePermission) return { permission: stepPermission };
|
|
29457
|
+
const rolePreset = yamlToolsPreset(roleDoc);
|
|
29458
|
+
const agentPreset = yamlToolsPreset(agent);
|
|
29459
|
+
const rolePresetProfile = rolePreset ? toolPresetProfile(rolePreset) : void 0;
|
|
29460
|
+
if (rolePreset && rolePresetProfile === "edit" && rolePermission === "read-only") {
|
|
29461
|
+
return {
|
|
29462
|
+
error: {
|
|
29463
|
+
reason: "step_unsupported",
|
|
29464
|
+
field: "tools.preset",
|
|
29465
|
+
detail: `tools.preset ${rolePreset} exceeds role ${roleId} permission read-only`
|
|
29466
|
+
}
|
|
29467
|
+
};
|
|
29468
|
+
}
|
|
29469
|
+
if (agentPreset) {
|
|
29470
|
+
const exceeds = toolPresetExceedsRoleMessage(agentPreset, roleId, rolePermission, rolePreset);
|
|
29471
|
+
if (exceeds) {
|
|
29472
|
+
return { error: { reason: "step_unsupported", field: "tools.preset", detail: exceeds } };
|
|
29473
|
+
}
|
|
29474
|
+
}
|
|
29475
|
+
let chain = rolePermission;
|
|
29476
|
+
if (rolePresetProfile) chain = narrowerPermission(chain, rolePresetProfile);
|
|
29477
|
+
const agentProfile = agentPreset ? toolPresetProfile(agentPreset) : void 0;
|
|
29478
|
+
if (agentProfile) chain = narrowerPermission(chain, agentProfile);
|
|
29479
|
+
const permission = narrowerPermission(chain, stepPermission);
|
|
29480
|
+
if (stepPermission === "edit" && permission === "read-only") {
|
|
29481
|
+
return {
|
|
29482
|
+
error: {
|
|
29483
|
+
reason: "step_unsupported",
|
|
29484
|
+
field: "permission",
|
|
29485
|
+
detail: `role permission ${rolePermission} resolves to ${chain}; step access ${stepAccessDetail(step)}`
|
|
29486
|
+
}
|
|
29487
|
+
};
|
|
29488
|
+
}
|
|
29489
|
+
return { permission };
|
|
29490
|
+
}
|
|
29380
29491
|
function executingSingletonAttempt(plan, state) {
|
|
29381
29492
|
const current = state.currentStep;
|
|
29382
29493
|
if (!current || current.panel.order.length !== 1) return void 0;
|
|
@@ -29569,10 +29680,17 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
29569
29680
|
if (unsupported) return { kind: "return", state, handoff: { ...unsupported, stepId } };
|
|
29570
29681
|
const wideTimeout = unsupportedAgentStepTimeout(step, projectAgentStepTimeoutMs(context, run));
|
|
29571
29682
|
if (wideTimeout) return { kind: "return", state, handoff: { ...wideTimeout, stepId } };
|
|
29683
|
+
const firstListed = step.assignments.allowedAgents[0];
|
|
29684
|
+
const firstAgentId = step.kind === "agent" || step.kind === "moa" ? firstListed ? firstListed : step.agent : void 0;
|
|
29685
|
+
if (firstAgentId) {
|
|
29686
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, step, firstAgentId);
|
|
29687
|
+
if ("error" in toolDispatch) {
|
|
29688
|
+
return { kind: "return", state, handoff: { ...toolDispatch.error, stepId } };
|
|
29689
|
+
}
|
|
29690
|
+
}
|
|
29572
29691
|
let resolvedRoute;
|
|
29573
29692
|
if (producerId !== "driver-simulated") {
|
|
29574
|
-
const
|
|
29575
|
-
const agentId = allowed && allowed.length > 0 && allowed[0] ? allowed[0] : step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator";
|
|
29693
|
+
const agentId = firstAgentId ?? (step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator");
|
|
29576
29694
|
const routeResult = resolveProducerRoute(context.projectRoot, step, agentId);
|
|
29577
29695
|
if ("error" in routeResult) {
|
|
29578
29696
|
return { kind: "return", state, handoff: { ...routeResult.error, stepId } };
|
|
@@ -29692,6 +29810,10 @@ function birthMember(context, input) {
|
|
|
29692
29810
|
const born = folded.currentStep?.panel.order.length ?? 0;
|
|
29693
29811
|
const allowed = input.step.assignments.allowedAgents;
|
|
29694
29812
|
const agentId = allowed && allowed.length > born && allowed[born] ? allowed[born] : input.step.kind === "agent" || input.step.kind === "moa" ? input.step.agent : "coordinator";
|
|
29813
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, input.step, agentId);
|
|
29814
|
+
if ("error" in toolDispatch) {
|
|
29815
|
+
return { handoff: { ...toolDispatch.error, stepId: input.stepId } };
|
|
29816
|
+
}
|
|
29695
29817
|
let resolvedRoute = input.resolvedRoute;
|
|
29696
29818
|
if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
|
|
29697
29819
|
const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
|
|
@@ -29781,7 +29903,7 @@ function birthMember(context, input) {
|
|
|
29781
29903
|
stepAttempt: input.stepAttempt,
|
|
29782
29904
|
objective: promptText,
|
|
29783
29905
|
allowedOutcomes: [...input.step.outcomes],
|
|
29784
|
-
permissionCeiling:
|
|
29906
|
+
permissionCeiling: toolDispatch.permission
|
|
29785
29907
|
},
|
|
29786
29908
|
acceptanceCriteria: input.step.requiredEvidence.map((ev) => ({
|
|
29787
29909
|
id: ev.key,
|
|
@@ -29826,7 +29948,7 @@ function birthMember(context, input) {
|
|
|
29826
29948
|
prompt: input.step.instructions ? `${input.step.instructions}
|
|
29827
29949
|
|
|
29828
29950
|
${generatedPrompt}` : generatedPrompt,
|
|
29829
|
-
permission:
|
|
29951
|
+
permission: toolDispatch.permission,
|
|
29830
29952
|
...(input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== void 0 ? { timeoutMs: input.step.timeoutMs } : {},
|
|
29831
29953
|
contextPacket,
|
|
29832
29954
|
...resolvedRoute?.effort !== void 0 ? { thinking: resolvedRoute.effort } : {},
|
|
@@ -17431,7 +17431,28 @@ var RESOURCE_SCHEMA = Object.freeze({
|
|
|
17431
17431
|
var IDENTIFIER2 = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
17432
17432
|
var WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
|
|
17433
17433
|
var BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
|
|
17434
|
-
var
|
|
17434
|
+
var TOOL_PRESET_PROFILES = Object.freeze({
|
|
17435
|
+
coordinator: "read-only",
|
|
17436
|
+
"read-only": "read-only",
|
|
17437
|
+
"workspace-writer": "edit",
|
|
17438
|
+
"tests-writer": "edit"
|
|
17439
|
+
});
|
|
17440
|
+
var BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
|
|
17441
|
+
function toolPresetProfile(preset) {
|
|
17442
|
+
if (!Object.hasOwn(TOOL_PRESET_PROFILES, preset)) return void 0;
|
|
17443
|
+
return TOOL_PRESET_PROFILES[preset];
|
|
17444
|
+
}
|
|
17445
|
+
function toolPresetExceedsRoleMessage(preset, roleId, rolePermission, rolePreset) {
|
|
17446
|
+
const profile = toolPresetProfile(preset);
|
|
17447
|
+
if (profile !== "edit") return void 0;
|
|
17448
|
+
if (rolePermission === "read-only") {
|
|
17449
|
+
return `tools.preset ${preset} exceeds role ${roleId} permission read-only`;
|
|
17450
|
+
}
|
|
17451
|
+
if (rolePreset !== void 0 && toolPresetProfile(rolePreset) === "read-only") {
|
|
17452
|
+
return `tools.preset ${preset} exceeds role ${roleId} preset ${rolePreset}`;
|
|
17453
|
+
}
|
|
17454
|
+
return void 0;
|
|
17455
|
+
}
|
|
17435
17456
|
var SECRET_VALUE_PATTERNS = [
|
|
17436
17457
|
/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
|
|
17437
17458
|
/\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b/,
|
|
@@ -17831,6 +17852,15 @@ function objectValue(value) {
|
|
|
17831
17852
|
function stringValue(value) {
|
|
17832
17853
|
return typeof value === "string" ? value : void 0;
|
|
17833
17854
|
}
|
|
17855
|
+
function roleById(roles, roleId) {
|
|
17856
|
+
const direct = roles.get(roleId);
|
|
17857
|
+
if (direct) return direct;
|
|
17858
|
+
const folded = roleId.toLocaleLowerCase("en-US");
|
|
17859
|
+
for (const role of roles.values()) {
|
|
17860
|
+
if ((role.id ?? "").toLocaleLowerCase("en-US") === folded) return role;
|
|
17861
|
+
}
|
|
17862
|
+
return void 0;
|
|
17863
|
+
}
|
|
17834
17864
|
function numberValue(value, fallback) {
|
|
17835
17865
|
return typeof value === "number" ? value : fallback;
|
|
17836
17866
|
}
|
|
@@ -18345,10 +18375,29 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
18345
18375
|
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
18346
18376
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
18347
18377
|
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
18378
|
+
const roleId = stringValue(agent.value.role);
|
|
18379
|
+
const role = roleId ? roleById(roles, roleId) : void 0;
|
|
18380
|
+
const rolePermission = stringValue(role?.value.permission);
|
|
18381
|
+
if (preset && role && (rolePermission === "edit" || rolePermission === "read-only")) {
|
|
18382
|
+
const message = toolPresetExceedsRoleMessage(
|
|
18383
|
+
preset,
|
|
18384
|
+
role.id ?? roleId ?? "role",
|
|
18385
|
+
rolePermission,
|
|
18386
|
+
stringValue(objectValue(role.value.tools)?.preset)
|
|
18387
|
+
);
|
|
18388
|
+
if (message) issues.push(issue3("semantic", "tool_preset_exceeds_role", agent.logicalPath, message));
|
|
18389
|
+
}
|
|
18348
18390
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
18349
18391
|
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
18350
18392
|
}
|
|
18351
18393
|
}
|
|
18394
|
+
for (const role of roles.values()) {
|
|
18395
|
+
const permission = stringValue(role.value.permission);
|
|
18396
|
+
const rolePreset = stringValue(objectValue(role.value.tools)?.preset);
|
|
18397
|
+
if (permission === "read-only" && rolePreset && toolPresetProfile(rolePreset) === "edit") {
|
|
18398
|
+
issues.push(issue3("semantic", "tool_preset_exceeds_role", role.logicalPath, `tools.preset ${rolePreset} exceeds permission read-only`));
|
|
18399
|
+
}
|
|
18400
|
+
}
|
|
18352
18401
|
validateModelReferences(models, issues);
|
|
18353
18402
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
18354
18403
|
if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
@@ -30125,6 +30174,68 @@ function unsupportedAgentStepTimeout(step, limitMs) {
|
|
|
30125
30174
|
detail: "step timeoutMs is wider than project limits.agentStepTimeoutMs"
|
|
30126
30175
|
};
|
|
30127
30176
|
}
|
|
30177
|
+
function narrowerPermission(left, right) {
|
|
30178
|
+
return left === "read-only" || right === "read-only" ? "read-only" : "edit";
|
|
30179
|
+
}
|
|
30180
|
+
function stepRepositoryPermission(step) {
|
|
30181
|
+
return Object.values(step.repositories).some((access) => access === "write") ? "edit" : "read-only";
|
|
30182
|
+
}
|
|
30183
|
+
function stepAccessDetail(step) {
|
|
30184
|
+
const entries = Object.entries(step.repositories);
|
|
30185
|
+
if (entries.length === 0) return "none";
|
|
30186
|
+
return entries.map(([id, access]) => `${id} ${access}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0).join(", ");
|
|
30187
|
+
}
|
|
30188
|
+
function yamlToolsPreset(doc) {
|
|
30189
|
+
const tools = doc?.tools;
|
|
30190
|
+
if (!tools || typeof tools !== "object" || Array.isArray(tools)) return void 0;
|
|
30191
|
+
const preset = tools.preset;
|
|
30192
|
+
return typeof preset === "string" && preset.length > 0 ? preset : void 0;
|
|
30193
|
+
}
|
|
30194
|
+
function resolveToolDispatch(projectRoot, step, agentId) {
|
|
30195
|
+
const stepPermission = stepRepositoryPermission(step);
|
|
30196
|
+
if (step.kind !== "agent" && step.kind !== "moa") return { permission: stepPermission };
|
|
30197
|
+
const agentFile = findYamlBasename(join18(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
30198
|
+
const agent = readYamlFile(join18(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
30199
|
+
const roleId = typeof agent?.role === "string" ? agent.role : "";
|
|
30200
|
+
if (!roleId) return { permission: stepPermission };
|
|
30201
|
+
const roleDoc = readYamlFile(join18(projectRoot, ".kxm", "roles", `${roleId}.yaml`));
|
|
30202
|
+
const rawPermission = roleDoc?.permission;
|
|
30203
|
+
const rolePermission = rawPermission === "edit" || rawPermission === "read-only" ? rawPermission : void 0;
|
|
30204
|
+
if (!rolePermission) return { permission: stepPermission };
|
|
30205
|
+
const rolePreset = yamlToolsPreset(roleDoc);
|
|
30206
|
+
const agentPreset = yamlToolsPreset(agent);
|
|
30207
|
+
const rolePresetProfile = rolePreset ? toolPresetProfile(rolePreset) : void 0;
|
|
30208
|
+
if (rolePreset && rolePresetProfile === "edit" && rolePermission === "read-only") {
|
|
30209
|
+
return {
|
|
30210
|
+
error: {
|
|
30211
|
+
reason: "step_unsupported",
|
|
30212
|
+
field: "tools.preset",
|
|
30213
|
+
detail: `tools.preset ${rolePreset} exceeds role ${roleId} permission read-only`
|
|
30214
|
+
}
|
|
30215
|
+
};
|
|
30216
|
+
}
|
|
30217
|
+
if (agentPreset) {
|
|
30218
|
+
const exceeds = toolPresetExceedsRoleMessage(agentPreset, roleId, rolePermission, rolePreset);
|
|
30219
|
+
if (exceeds) {
|
|
30220
|
+
return { error: { reason: "step_unsupported", field: "tools.preset", detail: exceeds } };
|
|
30221
|
+
}
|
|
30222
|
+
}
|
|
30223
|
+
let chain = rolePermission;
|
|
30224
|
+
if (rolePresetProfile) chain = narrowerPermission(chain, rolePresetProfile);
|
|
30225
|
+
const agentProfile = agentPreset ? toolPresetProfile(agentPreset) : void 0;
|
|
30226
|
+
if (agentProfile) chain = narrowerPermission(chain, agentProfile);
|
|
30227
|
+
const permission = narrowerPermission(chain, stepPermission);
|
|
30228
|
+
if (stepPermission === "edit" && permission === "read-only") {
|
|
30229
|
+
return {
|
|
30230
|
+
error: {
|
|
30231
|
+
reason: "step_unsupported",
|
|
30232
|
+
field: "permission",
|
|
30233
|
+
detail: `role permission ${rolePermission} resolves to ${chain}; step access ${stepAccessDetail(step)}`
|
|
30234
|
+
}
|
|
30235
|
+
};
|
|
30236
|
+
}
|
|
30237
|
+
return { permission };
|
|
30238
|
+
}
|
|
30128
30239
|
function executingSingletonAttempt(plan, state) {
|
|
30129
30240
|
const current = state.currentStep;
|
|
30130
30241
|
if (!current || current.panel.order.length !== 1) return void 0;
|
|
@@ -30317,10 +30428,17 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
30317
30428
|
if (unsupported) return { kind: "return", state, handoff: { ...unsupported, stepId } };
|
|
30318
30429
|
const wideTimeout = unsupportedAgentStepTimeout(step, projectAgentStepTimeoutMs(context, run));
|
|
30319
30430
|
if (wideTimeout) return { kind: "return", state, handoff: { ...wideTimeout, stepId } };
|
|
30431
|
+
const firstListed = step.assignments.allowedAgents[0];
|
|
30432
|
+
const firstAgentId = step.kind === "agent" || step.kind === "moa" ? firstListed ? firstListed : step.agent : void 0;
|
|
30433
|
+
if (firstAgentId) {
|
|
30434
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, step, firstAgentId);
|
|
30435
|
+
if ("error" in toolDispatch) {
|
|
30436
|
+
return { kind: "return", state, handoff: { ...toolDispatch.error, stepId } };
|
|
30437
|
+
}
|
|
30438
|
+
}
|
|
30320
30439
|
let resolvedRoute;
|
|
30321
30440
|
if (producerId !== "driver-simulated") {
|
|
30322
|
-
const
|
|
30323
|
-
const agentId = allowed && allowed.length > 0 && allowed[0] ? allowed[0] : step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator";
|
|
30441
|
+
const agentId = firstAgentId ?? (step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator");
|
|
30324
30442
|
const routeResult = resolveProducerRoute(context.projectRoot, step, agentId);
|
|
30325
30443
|
if ("error" in routeResult) {
|
|
30326
30444
|
return { kind: "return", state, handoff: { ...routeResult.error, stepId } };
|
|
@@ -30440,6 +30558,10 @@ function birthMember(context, input) {
|
|
|
30440
30558
|
const born = folded.currentStep?.panel.order.length ?? 0;
|
|
30441
30559
|
const allowed = input.step.assignments.allowedAgents;
|
|
30442
30560
|
const agentId = allowed && allowed.length > born && allowed[born] ? allowed[born] : input.step.kind === "agent" || input.step.kind === "moa" ? input.step.agent : "coordinator";
|
|
30561
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, input.step, agentId);
|
|
30562
|
+
if ("error" in toolDispatch) {
|
|
30563
|
+
return { handoff: { ...toolDispatch.error, stepId: input.stepId } };
|
|
30564
|
+
}
|
|
30443
30565
|
let resolvedRoute = input.resolvedRoute;
|
|
30444
30566
|
if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
|
|
30445
30567
|
const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
|
|
@@ -30529,7 +30651,7 @@ function birthMember(context, input) {
|
|
|
30529
30651
|
stepAttempt: input.stepAttempt,
|
|
30530
30652
|
objective: promptText,
|
|
30531
30653
|
allowedOutcomes: [...input.step.outcomes],
|
|
30532
|
-
permissionCeiling:
|
|
30654
|
+
permissionCeiling: toolDispatch.permission
|
|
30533
30655
|
},
|
|
30534
30656
|
acceptanceCriteria: input.step.requiredEvidence.map((ev) => ({
|
|
30535
30657
|
id: ev.key,
|
|
@@ -30574,7 +30696,7 @@ function birthMember(context, input) {
|
|
|
30574
30696
|
prompt: input.step.instructions ? `${input.step.instructions}
|
|
30575
30697
|
|
|
30576
30698
|
${generatedPrompt}` : generatedPrompt,
|
|
30577
|
-
permission:
|
|
30699
|
+
permission: toolDispatch.permission,
|
|
30578
30700
|
...(input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== void 0 ? { timeoutMs: input.step.timeoutMs } : {},
|
|
30579
30701
|
contextPacket,
|
|
30580
30702
|
...resolvedRoute?.effort !== void 0 ? { thinking: resolvedRoute.effort } : {},
|
|
@@ -15542,6 +15542,13 @@ var RESOURCE_SCHEMA = Object.freeze({
|
|
|
15542
15542
|
workflow: { identity: "kxm.workflow.v1", file: "workflow.schema.json" },
|
|
15543
15543
|
"gate-registry": { identity: "kxm.gate-registry.v1", file: "gate-registry.schema.json" }
|
|
15544
15544
|
});
|
|
15545
|
+
var TOOL_PRESET_PROFILES = Object.freeze({
|
|
15546
|
+
coordinator: "read-only",
|
|
15547
|
+
"read-only": "read-only",
|
|
15548
|
+
"workspace-writer": "edit",
|
|
15549
|
+
"tests-writer": "edit"
|
|
15550
|
+
});
|
|
15551
|
+
var BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
|
|
15545
15552
|
function compareCodeUnits(left, right) {
|
|
15546
15553
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
15547
15554
|
}
|
package/plugins/kxm/package.json
CHANGED
|
@@ -12,8 +12,9 @@ or permission to execute an assignment.
|
|
|
12
12
|
|
|
13
13
|
Dispatch reads `.kxm/roles/*.yaml` and `.kxm/models/*.yaml`. An agent binds
|
|
14
14
|
a role with `role:` and does not pin `harness` or `model`. An agent
|
|
15
|
-
`tools.preset` must be a registered preset
|
|
16
|
-
|
|
15
|
+
`tools.preset` must be a registered preset and may only narrow the role.
|
|
16
|
+
A wider preset is refused at load (`tool_preset_exceeds_role`) and again
|
|
17
|
+
at dispatch (`step_unsupported`). `kxm routes` prints
|
|
17
18
|
admission from `.kxm/routes.yaml` and membership from the role files.
|
|
18
19
|
The developer runner builds its policy object from those same files at
|
|
19
20
|
`refs/remotes/origin/main`.
|
|
@@ -113,7 +113,7 @@ import {
|
|
|
113
113
|
type KxmRunRecord,
|
|
114
114
|
type KxmRunStatus,
|
|
115
115
|
} from "./runtime-store.ts";
|
|
116
|
-
import { KxmConfigError, kxmCanonicalJson, lookupKxmResource, type JsonValue, type KxmProjectBundle } from "./project-config.ts";
|
|
116
|
+
import { KxmConfigError, kxmCanonicalJson, lookupKxmResource, toolPresetExceedsRoleMessage, toolPresetProfile, type JsonValue, type KxmProjectBundle } from "./project-config.ts";
|
|
117
117
|
import { evaluateArtifactsGate } from "./engine-artifacts.ts";
|
|
118
118
|
import { createCommandObserver } from "./engine-command.ts";
|
|
119
119
|
import {
|
|
@@ -1726,6 +1726,85 @@ function unsupportedAgentStepTimeout(step: KxmCompiledStep, limitMs: number): Om
|
|
|
1726
1726
|
};
|
|
1727
1727
|
}
|
|
1728
1728
|
|
|
1729
|
+
function narrowerPermission(left: "read-only" | "edit", right: "read-only" | "edit"): "read-only" | "edit" {
|
|
1730
|
+
return left === "read-only" || right === "read-only" ? "read-only" : "edit";
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function stepRepositoryPermission(step: KxmCompiledStep): "read-only" | "edit" {
|
|
1734
|
+
return Object.values(step.repositories).some((access) => access === "write") ? "edit" : "read-only";
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function stepAccessDetail(step: KxmCompiledStep): string {
|
|
1738
|
+
const entries = Object.entries(step.repositories);
|
|
1739
|
+
if (entries.length === 0) return "none";
|
|
1740
|
+
return entries
|
|
1741
|
+
.map(([id, access]) => `${id} ${access}`)
|
|
1742
|
+
.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))
|
|
1743
|
+
.join(", ");
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
function yamlToolsPreset(doc: Record<string, unknown> | undefined): string | undefined {
|
|
1747
|
+
const tools = doc?.tools;
|
|
1748
|
+
if (!tools || typeof tools !== "object" || Array.isArray(tools)) return undefined;
|
|
1749
|
+
const preset = (tools as { preset?: unknown }).preset;
|
|
1750
|
+
return typeof preset === "string" && preset.length > 0 ? preset : undefined;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Dispatch permission is the narrowest of the role permission, the preset
|
|
1755
|
+
* profile, and the step repository access. A wider agent preset, or a write
|
|
1756
|
+
* step on a read-only chain, is step_unsupported.
|
|
1757
|
+
*/
|
|
1758
|
+
function resolveToolDispatch(
|
|
1759
|
+
projectRoot: string,
|
|
1760
|
+
step: KxmCompiledStep,
|
|
1761
|
+
agentId: string,
|
|
1762
|
+
): { permission: "read-only" | "edit" } | { error: Omit<KxmRunHandoff, "stepId"> } {
|
|
1763
|
+
const stepPermission = stepRepositoryPermission(step);
|
|
1764
|
+
if (step.kind !== "agent" && step.kind !== "moa") return { permission: stepPermission };
|
|
1765
|
+
const agentFile = findYamlBasename(join(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
1766
|
+
const agent = readYamlFile(join(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
1767
|
+
const roleId = typeof agent?.role === "string" ? agent.role : "";
|
|
1768
|
+
if (!roleId) return { permission: stepPermission };
|
|
1769
|
+
const roleDoc = readYamlFile(join(projectRoot, ".kxm", "roles", `${roleId}.yaml`));
|
|
1770
|
+
const rawPermission = roleDoc?.permission;
|
|
1771
|
+
const rolePermission: "edit" | "read-only" | undefined = rawPermission === "edit" || rawPermission === "read-only" ? rawPermission : undefined;
|
|
1772
|
+
if (!rolePermission) return { permission: stepPermission };
|
|
1773
|
+
const rolePreset = yamlToolsPreset(roleDoc);
|
|
1774
|
+
const agentPreset = yamlToolsPreset(agent);
|
|
1775
|
+
const rolePresetProfile = rolePreset ? toolPresetProfile(rolePreset) : undefined;
|
|
1776
|
+
if (rolePreset && rolePresetProfile === "edit" && rolePermission === "read-only") {
|
|
1777
|
+
return {
|
|
1778
|
+
error: {
|
|
1779
|
+
reason: "step_unsupported",
|
|
1780
|
+
field: "tools.preset",
|
|
1781
|
+
detail: `tools.preset ${rolePreset} exceeds role ${roleId} permission read-only`,
|
|
1782
|
+
},
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
if (agentPreset) {
|
|
1786
|
+
const exceeds = toolPresetExceedsRoleMessage(agentPreset, roleId, rolePermission, rolePreset);
|
|
1787
|
+
if (exceeds) {
|
|
1788
|
+
return { error: { reason: "step_unsupported", field: "tools.preset", detail: exceeds } };
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
let chain: "read-only" | "edit" = rolePermission;
|
|
1792
|
+
if (rolePresetProfile) chain = narrowerPermission(chain, rolePresetProfile);
|
|
1793
|
+
const agentProfile = agentPreset ? toolPresetProfile(agentPreset) : undefined;
|
|
1794
|
+
if (agentProfile) chain = narrowerPermission(chain, agentProfile);
|
|
1795
|
+
const permission = narrowerPermission(chain, stepPermission);
|
|
1796
|
+
if (stepPermission === "edit" && permission === "read-only") {
|
|
1797
|
+
return {
|
|
1798
|
+
error: {
|
|
1799
|
+
reason: "step_unsupported",
|
|
1800
|
+
field: "permission",
|
|
1801
|
+
detail: `role permission ${rolePermission} resolves to ${chain}; step access ${stepAccessDetail(step)}`,
|
|
1802
|
+
},
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
return { permission };
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1729
1808
|
function executingSingletonAttempt(
|
|
1730
1809
|
plan: KxmCompiledPlan,
|
|
1731
1810
|
state: KxmRunState,
|
|
@@ -1944,12 +2023,21 @@ function prepareDispatch(
|
|
|
1944
2023
|
const wideTimeout = unsupportedAgentStepTimeout(step, projectAgentStepTimeoutMs(context, run));
|
|
1945
2024
|
if (wideTimeout) return { kind: "return", state, handoff: { ...wideTimeout, stepId } };
|
|
1946
2025
|
|
|
2026
|
+
const firstListed = step.assignments.allowedAgents[0];
|
|
2027
|
+
const firstAgentId = (step.kind === "agent" || step.kind === "moa")
|
|
2028
|
+
? (firstListed ? firstListed : step.agent)
|
|
2029
|
+
: undefined;
|
|
2030
|
+
if (firstAgentId) {
|
|
2031
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, step, firstAgentId);
|
|
2032
|
+
if ("error" in toolDispatch) {
|
|
2033
|
+
return { kind: "return", state, handoff: { ...toolDispatch.error, stepId } };
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1947
2037
|
let resolvedRoute: ResolvedProducerRoute | undefined;
|
|
1948
2038
|
if (producerId !== "driver-simulated") {
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
? allowed[0]!
|
|
1952
|
-
: (step.kind === "agent" || step.kind === "moa" ? step.agent : "coordinator");
|
|
2039
|
+
const agentId = firstAgentId
|
|
2040
|
+
?? ((step.kind === "agent" || step.kind === "moa") ? step.agent : "coordinator");
|
|
1953
2041
|
const routeResult = resolveProducerRoute(context.projectRoot, step, agentId);
|
|
1954
2042
|
if ("error" in routeResult) {
|
|
1955
2043
|
return { kind: "return", state, handoff: { ...routeResult.error, stepId } };
|
|
@@ -2092,6 +2180,10 @@ function birthMember(
|
|
|
2092
2180
|
const agentId = (allowed && allowed.length > born && allowed[born])
|
|
2093
2181
|
? allowed[born]!
|
|
2094
2182
|
: (input.step.kind === "agent" || input.step.kind === "moa" ? input.step.agent : "coordinator");
|
|
2183
|
+
const toolDispatch = resolveToolDispatch(context.projectRoot, input.step, agentId);
|
|
2184
|
+
if ("error" in toolDispatch) {
|
|
2185
|
+
return { handoff: { ...toolDispatch.error, stepId: input.stepId } };
|
|
2186
|
+
}
|
|
2095
2187
|
let resolvedRoute = input.resolvedRoute;
|
|
2096
2188
|
if (!resolvedRoute && input.producerId && input.producerId !== "driver-simulated") {
|
|
2097
2189
|
const routeResult = resolveProducerRoute(context.projectRoot, input.step, agentId);
|
|
@@ -2183,7 +2275,7 @@ function birthMember(
|
|
|
2183
2275
|
stepAttempt: input.stepAttempt,
|
|
2184
2276
|
objective: promptText,
|
|
2185
2277
|
allowedOutcomes: [...input.step.outcomes],
|
|
2186
|
-
permissionCeiling:
|
|
2278
|
+
permissionCeiling: toolDispatch.permission,
|
|
2187
2279
|
},
|
|
2188
2280
|
acceptanceCriteria: input.step.requiredEvidence.map((ev) => ({
|
|
2189
2281
|
id: ev.key,
|
|
@@ -2227,7 +2319,7 @@ function birthMember(
|
|
|
2227
2319
|
allowedOutcomes: input.step.outcomes,
|
|
2228
2320
|
signal: controller.signal,
|
|
2229
2321
|
prompt: input.step.instructions ? `${input.step.instructions}\n\n${generatedPrompt}` : generatedPrompt,
|
|
2230
|
-
permission:
|
|
2322
|
+
permission: toolDispatch.permission,
|
|
2231
2323
|
...((input.step.kind === "agent" || input.step.kind === "moa") && input.step.timeoutMs !== undefined
|
|
2232
2324
|
? { timeoutMs: input.step.timeoutMs }
|
|
2233
2325
|
: {}),
|
|
@@ -3144,6 +3236,11 @@ export function kxmLiveRunPrerequisites(
|
|
|
3144
3236
|
if (step.kind !== "agent" && step.kind !== "moa") continue;
|
|
3145
3237
|
const agentIds = step.assignments.allowedAgents.length > 0 ? step.assignments.allowedAgents : [step.agent];
|
|
3146
3238
|
for (const agentId of agentIds) {
|
|
3239
|
+
const toolDispatch = resolveToolDispatch(projectRoot, step, agentId);
|
|
3240
|
+
if ("error" in toolDispatch) {
|
|
3241
|
+
prerequisites.push({ ...toolDispatch.error, stepId });
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3147
3244
|
const route = resolveProducerRoute(projectRoot, step, agentId);
|
|
3148
3245
|
if ("error" in route) {
|
|
3149
3246
|
const detail = route.error.detail === "producer_route_unsupported: agent step model is not honored"
|
|
@@ -3153,7 +3250,7 @@ export function kxmLiveRunPrerequisites(
|
|
|
3153
3250
|
continue;
|
|
3154
3251
|
}
|
|
3155
3252
|
const harness = route.harness;
|
|
3156
|
-
const permission =
|
|
3253
|
+
const permission = toolDispatch.permission;
|
|
3157
3254
|
if (!BUILTIN_HARNESSES.some((entry) => entry.id === harness && entry.oneShot) || !oneShotPermissionArgs(harness, permission)) {
|
|
3158
3255
|
prerequisites.push({ reason: "step_unsupported", stepId, field: "harness", detail: `${agentId}: ${harness} has no audited ${permission} one-shot profile; use a supported workflow or execute this work directly in ${harness}, without substituting another harness` });
|
|
3159
3256
|
continue;
|
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.169";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -104,7 +104,40 @@ const RESOURCE_SCHEMA: Readonly<Record<KxmResourceKind, { identity: string; file
|
|
|
104
104
|
const IDENTIFIER = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
105
105
|
const WINDOWS_RESERVED = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³]|conin\$|conout\$|clock\$)$/i;
|
|
106
106
|
const BUILTIN_EXECUTORS = ["local", "ssh", "exe-dev"];
|
|
107
|
-
|
|
107
|
+
/** Builtin preset to the harness one-shot profile. An agent preset may only narrow. */
|
|
108
|
+
export const TOOL_PRESET_PROFILES = Object.freeze({
|
|
109
|
+
coordinator: "read-only",
|
|
110
|
+
"read-only": "read-only",
|
|
111
|
+
"workspace-writer": "edit",
|
|
112
|
+
"tests-writer": "edit",
|
|
113
|
+
} as const);
|
|
114
|
+
const BUILTIN_TOOL_PRESETS = Object.keys(TOOL_PRESET_PROFILES);
|
|
115
|
+
|
|
116
|
+
export function toolPresetProfile(preset: string): "read-only" | "edit" | undefined {
|
|
117
|
+
if (!Object.hasOwn(TOOL_PRESET_PROFILES, preset)) return undefined;
|
|
118
|
+
return TOOL_PRESET_PROFILES[preset as keyof typeof TOOL_PRESET_PROFILES];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Set when `preset` is wider than the role permission, or wider than a
|
|
123
|
+
* registered role preset. Same text at load and at dispatch.
|
|
124
|
+
*/
|
|
125
|
+
export function toolPresetExceedsRoleMessage(
|
|
126
|
+
preset: string,
|
|
127
|
+
roleId: string,
|
|
128
|
+
rolePermission: "edit" | "read-only",
|
|
129
|
+
rolePreset: string | undefined,
|
|
130
|
+
): string | undefined {
|
|
131
|
+
const profile = toolPresetProfile(preset);
|
|
132
|
+
if (profile !== "edit") return undefined;
|
|
133
|
+
if (rolePermission === "read-only") {
|
|
134
|
+
return `tools.preset ${preset} exceeds role ${roleId} permission read-only`;
|
|
135
|
+
}
|
|
136
|
+
if (rolePreset !== undefined && toolPresetProfile(rolePreset) === "read-only") {
|
|
137
|
+
return `tools.preset ${preset} exceeds role ${roleId} preset ${rolePreset}`;
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
108
141
|
const SECRET_VALUE_PATTERNS: readonly RegExp[] = [
|
|
109
142
|
/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/,
|
|
110
143
|
/\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b/,
|
|
@@ -583,6 +616,16 @@ function stringValue(value: JsonValue | undefined): string | undefined {
|
|
|
583
616
|
return typeof value === "string" ? value : undefined;
|
|
584
617
|
}
|
|
585
618
|
|
|
619
|
+
function roleById(roles: ReadonlyMap<string, KxmResource>, roleId: string): KxmResource | undefined {
|
|
620
|
+
const direct = roles.get(roleId);
|
|
621
|
+
if (direct) return direct;
|
|
622
|
+
const folded = roleId.toLocaleLowerCase("en-US");
|
|
623
|
+
for (const role of roles.values()) {
|
|
624
|
+
if ((role.id ?? "").toLocaleLowerCase("en-US") === folded) return role;
|
|
625
|
+
}
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
|
|
586
629
|
function numberValue(value: JsonValue | undefined, fallback: number): number {
|
|
587
630
|
return typeof value === "number" ? value : fallback;
|
|
588
631
|
}
|
|
@@ -1224,10 +1267,29 @@ function validateBundle(
|
|
|
1224
1267
|
if (executor && !executors.has(executor)) issues.push(issue("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
1225
1268
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
1226
1269
|
if (preset && !presets.has(preset)) issues.push(issue("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
1270
|
+
const roleId = stringValue(agent.value.role);
|
|
1271
|
+
const role = roleId ? roleById(roles, roleId) : undefined;
|
|
1272
|
+
const rolePermission = stringValue(role?.value.permission);
|
|
1273
|
+
if (preset && role && (rolePermission === "edit" || rolePermission === "read-only")) {
|
|
1274
|
+
const message = toolPresetExceedsRoleMessage(
|
|
1275
|
+
preset,
|
|
1276
|
+
role.id ?? roleId ?? "role",
|
|
1277
|
+
rolePermission,
|
|
1278
|
+
stringValue(objectValue(role.value.tools)?.preset),
|
|
1279
|
+
);
|
|
1280
|
+
if (message) issues.push(issue("semantic", "tool_preset_exceeds_role", agent.logicalPath, message));
|
|
1281
|
+
}
|
|
1227
1282
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
1228
1283
|
if (!repositoryIds.has(repositoryId)) issues.push(issue("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
1229
1284
|
}
|
|
1230
1285
|
}
|
|
1286
|
+
for (const role of roles.values()) {
|
|
1287
|
+
const permission = stringValue(role.value.permission);
|
|
1288
|
+
const rolePreset = stringValue(objectValue(role.value.tools)?.preset);
|
|
1289
|
+
if (permission === "read-only" && rolePreset && toolPresetProfile(rolePreset) === "edit") {
|
|
1290
|
+
issues.push(issue("semantic", "tool_preset_exceeds_role", role.logicalPath, `tools.preset ${rolePreset} exceeds permission read-only`));
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1231
1293
|
validateModelReferences(models, issues);
|
|
1232
1294
|
|
|
1233
1295
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|