@bevel-software/platform-core-backend 0.28.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +15 -0
- package/agent-guide/skills.md +8 -0
- package/dist/__tests__/test-repositories.d.ts +3 -0
- package/dist/__tests__/test-repositories.d.ts.map +1 -0
- package/dist/__tests__/test-repositories.js +18 -0
- package/dist/__tests__/test-repositories.js.map +1 -0
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +4 -2
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/compose-embed.d.ts +35 -0
- package/dist/core/compose-embed.d.ts.map +1 -0
- package/dist/core/compose-embed.js +14 -0
- package/dist/core/compose-embed.js.map +1 -0
- package/dist/core/core-ports.d.ts +10 -0
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +8 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +4 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +57 -18
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +19 -6
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +17 -6
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +20 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +68 -15
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +282 -58
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +20 -6
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +1 -1
- package/dist/modules/auth/index.d.ts +15 -0
- package/dist/modules/auth/index.d.ts.map +1 -0
- package/dist/modules/auth/index.js +20 -0
- package/dist/modules/auth/index.js.map +1 -0
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +7 -1
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/embed/embed.interface.d.ts +22 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.d.ts +28 -14
- package/dist/modules/embed/embed.service.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.js +38 -17
- package/dist/modules/embed/embed.service.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +10 -2
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/git-dir.d.ts +21 -0
- package/dist/modules/kb-fs/git-dir.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-dir.js +58 -0
- package/dist/modules/kb-fs/git-dir.js.map +1 -0
- package/dist/modules/kb-fs/index.d.ts +9 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -0
- package/dist/modules/kb-fs/index.js +3 -0
- package/dist/modules/kb-fs/index.js.map +1 -0
- package/dist/modules/kb-fs/ref-files.d.ts +63 -0
- package/dist/modules/kb-fs/ref-files.d.ts.map +1 -0
- package/dist/modules/kb-fs/ref-files.js +249 -0
- package/dist/modules/kb-fs/ref-files.js.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +43 -8
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/plugins/teams.routes.d.ts +4 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -1
- package/dist/modules/plugins/teams.routes.js +9 -1
- package/dist/modules/plugins/teams.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +8 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +115 -11
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/default-branch-write.d.ts +69 -0
- package/dist/modules/tool-helpers/default-branch-write.d.ts.map +1 -0
- package/dist/modules/tool-helpers/default-branch-write.js +63 -0
- package/dist/modules/tool-helpers/default-branch-write.js.map +1 -0
- package/dist/modules/tool-helpers/index.d.ts +1 -0
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +1 -0
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts +2 -2
- package/dist/modules/workflow/file-lock.service.d.ts +27 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +70 -18
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +42 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +201 -24
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/index.d.ts +8 -0
- package/dist/modules/workflow/index.d.ts.map +1 -0
- package/dist/modules/workflow/index.js +8 -0
- package/dist/modules/workflow/index.js.map +1 -0
- package/dist/modules/workflow/pending-commits.service.d.ts +2 -17
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +4 -24
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +20 -1
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +5 -3
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +2 -2
- package/dist/modules/workspace/agent-access.gate.js +2 -2
- package/dist/modules/workspace/bounded-matcher.d.ts +79 -0
- package/dist/modules/workspace/bounded-matcher.d.ts.map +1 -0
- package/dist/modules/workspace/bounded-matcher.js +261 -0
- package/dist/modules/workspace/bounded-matcher.js.map +1 -0
- package/dist/modules/workspace/fetch-instant.d.ts +9 -0
- package/dist/modules/workspace/fetch-instant.d.ts.map +1 -0
- package/dist/modules/workspace/fetch-instant.js +13 -0
- package/dist/modules/workspace/fetch-instant.js.map +1 -0
- package/dist/modules/workspace/index.d.ts +17 -0
- package/dist/modules/workspace/index.d.ts.map +1 -0
- package/dist/modules/workspace/index.js +16 -0
- package/dist/modules/workspace/index.js.map +1 -0
- package/dist/modules/workspace/kb-repository.contract.d.ts +124 -0
- package/dist/modules/workspace/kb-repository.contract.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.contract.js +19 -0
- package/dist/modules/workspace/kb-repository.contract.js.map +1 -0
- package/dist/modules/workspace/kb-repository.d.ts +236 -0
- package/dist/modules/workspace/kb-repository.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.js +811 -0
- package/dist/modules/workspace/kb-repository.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +50 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.js +27 -0
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +0 -7
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +0 -5
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/set-aside-clone.d.ts +40 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -1
- package/dist/modules/workspace/set-aside-clone.js +139 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +99 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +407 -29
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +68 -32
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +240 -161
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -4
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +268 -356
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/workspace-id.d.ts +17 -0
- package/dist/shared/workspace-id.d.ts.map +1 -1
- package/dist/shared/workspace-id.js +24 -0
- package/dist/shared/workspace-id.js.map +1 -1
- package/dist/test-setup.js +4 -0
- package/dist/test-setup.js.map +1 -1
- package/mcp-app/page.html +142 -8
- package/package.json +3 -3
- package/src/__tests__/test-repositories.ts +22 -0
- package/src/__tests__/workspace-service.ts +69 -0
- package/src/core/__tests__/compose-embed.test.ts +65 -0
- package/src/core/__tests__/lifecycle.test.ts +45 -26
- package/src/core/catalog-revision.ts +4 -2
- package/src/core/compose-embed.ts +50 -0
- package/src/core/core-ports.ts +10 -0
- package/src/core/create-core-server.ts +8 -2
- package/src/core/create-core-services.ts +63 -24
- package/src/core/lifecycle.ts +34 -12
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +443 -25
- package/src/modules/access/__tests__/access-control.group-read.test.ts +73 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +66 -0
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/access-control.interface.ts +22 -0
- package/src/modules/access/access-control.service.ts +302 -58
- package/src/modules/access/access.routes.ts +22 -6
- package/src/modules/agent-instructions/shared-file-rules.ts +1 -1
- package/src/modules/auth/index.ts +29 -0
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.worktree.test.ts +76 -0
- package/src/modules/diff/diff.service.ts +6 -4
- package/src/modules/embed/__tests__/embed.service.test.ts +76 -3
- package/src/modules/embed/__tests__/embed.tools.test.ts +29 -0
- package/src/modules/embed/embed.interface.ts +24 -0
- package/src/modules/embed/embed.service.ts +44 -19
- package/src/modules/embed/embed.tools.ts +10 -2
- package/src/modules/kb-fs/__tests__/git-dir.test.ts +77 -0
- package/src/modules/kb-fs/__tests__/ref-files.test.ts +338 -0
- package/src/modules/kb-fs/git-dir.ts +53 -0
- package/src/modules/kb-fs/index.ts +8 -0
- package/src/modules/kb-fs/ref-files.ts +279 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +73 -6
- package/src/modules/plugins/__tests__/teams.routes.test.ts +55 -0
- package/src/modules/plugins/join-request-jobs.service.ts +45 -8
- package/src/modules/plugins/teams.routes.ts +14 -1
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +2 -2
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +2 -2
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
- package/src/modules/skills/skills.contract.ts +4 -0
- package/src/modules/skills/skills.tools.ts +120 -10
- package/src/modules/tool-auth/internal-token.service.ts +1 -1
- package/src/modules/tool-auth/tool-auth.middleware.ts +1 -2
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +2 -2
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +581 -0
- package/src/modules/tool-helpers/default-branch-write.ts +90 -0
- package/src/modules/tool-helpers/index.ts +5 -0
- package/src/modules/tool-helpers/tool-handler.ts +2 -2
- package/src/modules/tool-helpers/tool.contract.ts +2 -2
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +42 -3
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +13 -2
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +168 -1
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +102 -6
- package/src/modules/workflow/file-lock.service.ts +79 -18
- package/src/modules/workflow/git/__tests__/git.service.changedPathsMemo.test.ts +175 -0
- package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +42 -0
- package/src/modules/workflow/git/__tests__/git.service.worktrees.test.ts +284 -0
- package/src/modules/workflow/git/git.service.ts +207 -22
- package/src/modules/workflow/index.ts +7 -0
- package/src/modules/workflow/pending-commits.service.ts +4 -23
- package/src/modules/workflow/workflow-hooks.ts +1 -1
- package/src/modules/workflow/workflow.routes.ts +24 -1
- package/src/modules/workflow/workflow.service.ts +3 -2
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +2 -2
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +213 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +22 -7
- package/src/modules/workspace/__tests__/kb-repository.test.ts +481 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +14 -0
- package/src/modules/workspace/__tests__/routine-write-policy.test.ts +0 -11
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +12 -5
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +24 -23
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +620 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +15 -1
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +71 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +174 -309
- package/src/modules/workspace/agent-access.gate.ts +2 -2
- package/src/modules/workspace/bounded-matcher.ts +328 -0
- package/src/modules/workspace/fetch-instant.ts +13 -0
- package/src/modules/workspace/index.ts +26 -0
- package/src/modules/workspace/kb-repository.contract.ts +124 -0
- package/src/modules/workspace/kb-repository.ts +844 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +69 -0
- package/src/modules/workspace/routine-write-policy.ts +0 -15
- package/src/modules/workspace/set-aside-clone.ts +140 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +3 -2
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +12 -10
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +613 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +422 -29
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +4 -3
- package/src/modules/workspace/workspace.service.ts +247 -169
- package/src/modules/workspace/workspace.tools.ts +299 -385
- package/src/shared/workspace-id.ts +24 -0
- package/src/test-setup.ts +5 -0
|
@@ -154,7 +154,7 @@ describe('the skill tools read a branch when asked', () => {
|
|
|
154
154
|
skills: { name: string; unmerged?: boolean }[];
|
|
155
155
|
};
|
|
156
156
|
expect(listArgs).toEqual([['e@x', { branch: undefined }]]);
|
|
157
|
-
expect(body.skills).toEqual([{ name: 'rfi', description: 'RFI.', path: released.path }]);
|
|
157
|
+
expect(body.skills).toEqual([{ name: 'rfi', description: 'RFI.', path: released.path, canWrite: false }]);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
it('a blank branch is no branch at all: the released catalog answers', async () => {
|
|
@@ -52,6 +52,10 @@ export interface SkillSummary {
|
|
|
52
52
|
unmerged?: true;
|
|
53
53
|
/** With `unmerged`: the branch the skill was read from. */
|
|
54
54
|
branch?: string;
|
|
55
|
+
// No `canWrite` here, on purpose: whether the caller may change a skill
|
|
56
|
+
// directly is a per-caller verdict the agent tools add onto a COPY of each
|
|
57
|
+
// summary (`skills.tools.ts`). The catalog is cached across users, so a
|
|
58
|
+
// verdict stored on it would be served to the next caller.
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export interface Skill extends SkillSummary {
|
|
@@ -10,6 +10,7 @@ import type { ToolHandlerFactory } from '../tool-helpers/tool-handler.js';
|
|
|
10
10
|
import type { ISkillService } from './skills.contract.js';
|
|
11
11
|
import { EXTERNAL_KB_MANUAL_NAME } from '../tool-manuals/tool-manuals.contract.js';
|
|
12
12
|
import type { IAllowedToolsChecker } from './allowed-tools-check.js';
|
|
13
|
+
import { CAN_WRITE_CLAUSE, defaultBranchWriteVerdicts, type DefaultBranchWriteDeps } from '../tool-helpers/index.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* The optional `branch` both skill tools declare. Optional on purpose, unlike
|
|
@@ -82,7 +83,57 @@ export function registerSkillsTools(
|
|
|
82
83
|
skillService: ISkillService,
|
|
83
84
|
/** When given, a loaded skill carries `warnings` for `allowed-tools` entries that name no visible tool. */
|
|
84
85
|
allowedTools?: IAllowedToolsChecker,
|
|
86
|
+
/**
|
|
87
|
+
* Where `canWrite` is judged: the access layer and the default branch's
|
|
88
|
+
* workspace. Without it every verdict is `false` — the safe answer, which
|
|
89
|
+
* sends the agent to a change request rather than a direct write.
|
|
90
|
+
*/
|
|
91
|
+
writeVerdicts?: DefaultBranchWriteDeps,
|
|
85
92
|
): void {
|
|
93
|
+
/**
|
|
94
|
+
* Whether the caller may commit each repo-relative path directly on the
|
|
95
|
+
* default branch — one batch per call, whichever branch was read (see
|
|
96
|
+
* `defaultBranchWriteVerdicts`).
|
|
97
|
+
*/
|
|
98
|
+
const verdictsFor = (ctx: ToolContext, paths: string[], at?: string): Promise<Map<string, boolean>> =>
|
|
99
|
+
writeVerdicts
|
|
100
|
+
? defaultBranchWriteVerdicts(writeVerdicts, ctx, paths, { ref: at })
|
|
101
|
+
: Promise.resolve(new Map(paths.map((p) => [p, false])));
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Every repo-relative file `get_skill`'s `canWrite` covers: the skill's
|
|
105
|
+
* folder as COMMITTED on the default branch (`HEAD` of its clone), which is
|
|
106
|
+
* what a direct write is judged against — never the working tree, where a
|
|
107
|
+
* bundled file deleted but not yet committed would drop out of the verdict
|
|
108
|
+
* while `HEAD` still forbids changing it. A skill is committed there when
|
|
109
|
+
* its `SKILL.md` is — a folder of that name holding other tracked files is
|
|
110
|
+
* not the skill, and its files are not the skill's. A skill not committed
|
|
111
|
+
* there exists only on the branch read, and is judged by its files there,
|
|
112
|
+
* where they would land. The files are listed at ONE commit — the one
|
|
113
|
+
* `HEAD` names when asked, answered beside them — and the verdict is judged
|
|
114
|
+
* at that same commit, so a commit landing between the two cannot pair one
|
|
115
|
+
* snapshot's files with another's rules. Null — judged `false` — when
|
|
116
|
+
* `HEAD` names no commit or the listing fails (fail closed), and without
|
|
117
|
+
* one when the verdict cannot be anything but `false`.
|
|
118
|
+
*/
|
|
119
|
+
const skillFilesOnDefault = async (
|
|
120
|
+
ctx: ToolContext,
|
|
121
|
+
loaded: { path: string; files: string[] },
|
|
122
|
+
): Promise<{ paths: string[]; at: string } | null> => {
|
|
123
|
+
if (!writeVerdicts?.committedFiles || ctx.scope !== 'write') return null;
|
|
124
|
+
const workspaceId = writeVerdicts.defaultWorkspaceId();
|
|
125
|
+
let at: string | null;
|
|
126
|
+
let committed: string[];
|
|
127
|
+
try {
|
|
128
|
+
at = await writeVerdicts.committedFiles.commitAtRef(workspaceId, 'HEAD');
|
|
129
|
+
if (!at) return null;
|
|
130
|
+
committed = await writeVerdicts.committedFiles.listFilesAtRef(workspaceId, at, loaded.path);
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return { paths: committed.includes(skillMdOf(loaded)) ? committed : [skillMdOf(loaded), ...loaded.files], at };
|
|
135
|
+
};
|
|
136
|
+
|
|
86
137
|
registry.registerExternalTool((ctx) => buildListSkillsDef(skillService, ctx.userEmail));
|
|
87
138
|
registry.registerInternalTool((ctx) => buildListSkillsDef(skillService, ctx.userEmail));
|
|
88
139
|
registry.registerExternalTool((ctx) => buildGetSkillDef(skillService, ctx.userEmail));
|
|
@@ -96,9 +147,14 @@ export function registerSkillsTools(
|
|
|
96
147
|
router.post(
|
|
97
148
|
'/agent/tools/list_skills',
|
|
98
149
|
toolAuth,
|
|
99
|
-
toolHandler(async (args, ctx: ToolContext) =>
|
|
100
|
-
skills
|
|
101
|
-
|
|
150
|
+
toolHandler(async (args, ctx: ToolContext) => {
|
|
151
|
+
const skills = await skillService.listSkills(ctx.user.email, { branch: branchArg(args) });
|
|
152
|
+
// Judged on each skill's SKILL.md, by the default branch's rules — a
|
|
153
|
+
// skill only on a draft is judged where it would land. Each verdict goes
|
|
154
|
+
// onto a COPY: the summaries may be the catalog shared across users.
|
|
155
|
+
const verdicts = await verdictsFor(ctx, skills.map(skillMdOf));
|
|
156
|
+
return { skills: skills.map((s) => ({ ...s, canWrite: verdicts.get(skillMdOf(s)) === true })) };
|
|
157
|
+
}),
|
|
102
158
|
);
|
|
103
159
|
|
|
104
160
|
router.post(
|
|
@@ -114,7 +170,8 @@ export function registerSkillsTools(
|
|
|
114
170
|
// skill's history, a `branch` is a draft nobody has released — asked for
|
|
115
171
|
// together, neither answer is the one the caller meant, and guessing
|
|
116
172
|
// would serve instructions under a label that does not describe them.
|
|
117
|
-
|
|
173
|
+
const versioned = version !== undefined && version.trim().length > 0;
|
|
174
|
+
if (branch !== undefined && versioned) {
|
|
118
175
|
throw new ToolError(
|
|
119
176
|
'get_skill takes `branch` or `version`, not both: `branch` loads the skill as that draft has it ' +
|
|
120
177
|
'now, `version` loads a version the released skill declared. Pass one of them.',
|
|
@@ -122,12 +179,33 @@ export function registerSkillsTools(
|
|
|
122
179
|
);
|
|
123
180
|
}
|
|
124
181
|
const result = await skillService.getSkill(ctx.user.email, name, file, { version, branch });
|
|
125
|
-
if (!
|
|
126
|
-
|
|
182
|
+
if (!result.ok) return result;
|
|
183
|
+
if (result.kind === 'file') {
|
|
184
|
+
const verdicts = await verdictsFor(ctx, [result.file.path]);
|
|
185
|
+
return { ...result, file: { ...result.file, canWrite: verdicts.get(result.file.path) === true } };
|
|
186
|
+
}
|
|
187
|
+
// The whole skill: true only when every one of its files may be written —
|
|
188
|
+
// the files the skill has COMMITTED on the default branch, so a draft, an
|
|
189
|
+
// older version, or a deletion not yet committed that drops a
|
|
190
|
+
// write-denied bundled file does not turn the verdict true. A skill only
|
|
191
|
+
// on the draft is judged by its draft files, where they would land.
|
|
192
|
+
const committed = await skillFilesOnDefault(ctx, result.skill);
|
|
193
|
+
const verdicts = committed === null ? new Map<string, boolean>() : await verdictsFor(ctx, committed.paths, committed.at);
|
|
194
|
+
const skill = {
|
|
195
|
+
...result.skill,
|
|
196
|
+
canWrite: committed !== null && committed.paths.every((p) => verdicts.get(p) === true),
|
|
197
|
+
};
|
|
198
|
+
if (!allowedTools) return { ...result, skill };
|
|
199
|
+
return { ...result, skill, warnings: await allowedTools.check(ctx.user.email, result.skill.allowedTools) };
|
|
127
200
|
}),
|
|
128
201
|
);
|
|
129
202
|
}
|
|
130
203
|
|
|
204
|
+
/** The repo-relative `SKILL.md` of a skill, which `list_skills` judges it by. */
|
|
205
|
+
function skillMdOf(skill: { path: string }): string {
|
|
206
|
+
return `${skill.path}/SKILL.md`;
|
|
207
|
+
}
|
|
208
|
+
|
|
131
209
|
/**
|
|
132
210
|
* The `branch` a tool call named, or undefined when it named none (the
|
|
133
211
|
* released catalog). A non-string or blank value is NOT a branch: it is taken
|
|
@@ -198,7 +276,9 @@ const LIST_SKILLS_DESCRIPTION =
|
|
|
198
276
|
'Discover what skills exist before specialist work, then `get_skill` to load one. ' +
|
|
199
277
|
'Pass `branch` to list the skills as they are on a draft branch instead of the released set — ' +
|
|
200
278
|
'what you need to try a skill you just wrote there; each skill that differs from the released ' +
|
|
201
|
-
'one comes back with `unmerged: true` and that branch, meaning nobody has approved it. '
|
|
279
|
+
'one comes back with `unmerged: true` and that branch, meaning nobody has approved it. ' +
|
|
280
|
+
'Each skill carries `canWrite`: whether you may change its SKILL.md directly on the default branch. ' +
|
|
281
|
+
`${CAN_WRITE_CLAUSE} `;
|
|
202
282
|
|
|
203
283
|
async function buildListSkillsDef(skillService: ISkillService, userEmail?: string): Promise<UtcpTool> {
|
|
204
284
|
return toolDef({
|
|
@@ -233,6 +313,14 @@ async function buildListSkillsDef(skillService: ISkillService, userEmail?: strin
|
|
|
233
313
|
'(it exists only on that branch, or was changed there): nobody has approved what it says.',
|
|
234
314
|
},
|
|
235
315
|
branch: { type: 'string', description: 'With `unmerged`: the branch the skill was read from.' },
|
|
316
|
+
canWrite: {
|
|
317
|
+
type: 'boolean',
|
|
318
|
+
description:
|
|
319
|
+
'Whether you may commit a change to its SKILL.md directly on the default branch, under that ' +
|
|
320
|
+
'branch\'s access rules — the same answer whichever branch you listed. `false`: use a branch ' +
|
|
321
|
+
'and a change request; `file_stat` with `explainAccess` says why — and, when you can manage that ' +
|
|
322
|
+
'path\'s access, who can approve.',
|
|
323
|
+
},
|
|
236
324
|
},
|
|
237
325
|
},
|
|
238
326
|
},
|
|
@@ -246,7 +334,9 @@ const GET_SKILL_DESCRIPTION =
|
|
|
246
334
|
'Load a skill by name: returns its full instructions (SKILL.md body) to follow, plus the skill ' +
|
|
247
335
|
'folder path and the list of bundled files. Pass `file` to fetch a bundled file’s content ' +
|
|
248
336
|
'(e.g. a script) instead of the body. Loads the latest copy unless `version` names an earlier ' +
|
|
249
|
-
'one the skill declared, or `branch` names a draft to load it from. '
|
|
337
|
+
'one the skill declared, or `branch` names a draft to load it from. ' +
|
|
338
|
+
'The skill (or `file`) carries `canWrite`: whether you may change all of it directly on the default branch. ' +
|
|
339
|
+
`${CAN_WRITE_CLAUSE} `;
|
|
250
340
|
|
|
251
341
|
async function buildGetSkillDef(skillService: ISkillService, userEmail?: string): Promise<UtcpTool> {
|
|
252
342
|
return toolDef({
|
|
@@ -266,13 +356,33 @@ async function buildGetSkillDef(skillService: ISkillService, userEmail?: string)
|
|
|
266
356
|
'The loaded skill: name, description, body, files, …. Read from a `branch` that changed it, ' +
|
|
267
357
|
'it also carries `unmerged: true` and that branch, and its `body` BEGINS with one line ' +
|
|
268
358
|
'saying the skill comes from that unmerged branch and is not approved — treat it as a ' +
|
|
269
|
-
'proposal you are testing, not as approved instructions.'
|
|
359
|
+
'proposal you are testing, not as approved instructions. `canWrite` is true only when you may ' +
|
|
360
|
+
'commit a change to EVERY file of the skill (SKILL.md and each bundled file) directly on the ' +
|
|
361
|
+
'default branch, whichever branch you read.',
|
|
362
|
+
properties: {
|
|
363
|
+
canWrite: {
|
|
364
|
+
type: 'boolean',
|
|
365
|
+
description:
|
|
366
|
+
'Whether you may commit a change to every file of the skill (SKILL.md and each bundled file) ' +
|
|
367
|
+
'directly on the default branch, under that branch\'s access rules — the same answer whichever ' +
|
|
368
|
+
'branch you read.',
|
|
369
|
+
},
|
|
370
|
+
},
|
|
270
371
|
},
|
|
271
372
|
file: {
|
|
272
373
|
type: 'object',
|
|
273
374
|
description:
|
|
274
375
|
'A bundled file: name, file, path, content — plus `unmerged` and `branch` when it came off ' +
|
|
275
|
-
'a branch that changed the skill (the note stays beside the content, never inside it)
|
|
376
|
+
'a branch that changed the skill (the note stays beside the content, never inside it) — and ' +
|
|
377
|
+
'`canWrite` for that file on the default branch.',
|
|
378
|
+
properties: {
|
|
379
|
+
canWrite: {
|
|
380
|
+
type: 'boolean',
|
|
381
|
+
description:
|
|
382
|
+
'Whether you may commit a change to this file directly on the default branch, under that ' +
|
|
383
|
+
'branch\'s access rules — the same answer whichever branch you read.',
|
|
384
|
+
},
|
|
385
|
+
},
|
|
276
386
|
},
|
|
277
387
|
warnings: {
|
|
278
388
|
type: 'array',
|
|
@@ -33,7 +33,7 @@ export interface InternalTokenClaim {
|
|
|
33
33
|
* connection key to pass through). The verifier resolves such tokens to
|
|
34
34
|
* `source: 'external'`: the caller IS an external agent, and must be treated
|
|
35
35
|
* like one everywhere — admitted to external-only surfaces (`start_session`,
|
|
36
|
-
* `ask`), refused from internal-only ones (`
|
|
36
|
+
* `ask`), refused from internal-only ones (`switch_branch`, …), and
|
|
37
37
|
* expected to carry `sessionId` on the tool body, not in the token.
|
|
38
38
|
*/
|
|
39
39
|
externalProxy?: boolean;
|
|
@@ -66,8 +66,7 @@ const WWW_AUTH = 'Bearer realm="bevel-tools"';
|
|
|
66
66
|
* Gate that admits ONLY in-process callers (those bearing an internal HMAC
|
|
67
67
|
* token). Mount it AFTER `toolAuth` on every internal-only surface — the
|
|
68
68
|
* internal tool catalog and the routes of tools registered with
|
|
69
|
-
* `registerInternalTool` but NOT `registerExternalTool` (e.g. `
|
|
70
|
-
* `switch_branch`, the `sharepoint_*` reads). `toolAuth` alone is not enough:
|
|
69
|
+
* `registerInternalTool` but NOT `registerExternalTool` (e.g. `switch_branch`, the `sharepoint_*` reads). `toolAuth` alone is not enough:
|
|
71
70
|
* an external connection key authenticates successfully, so without this gate it
|
|
72
71
|
* could invoke an internal-only route directly by guessing its name. The route
|
|
73
72
|
* MUST stay mounted (our own agent reaches it over the same loopback HTTP), so
|
|
@@ -8,7 +8,6 @@ import express from 'express';
|
|
|
8
8
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
9
9
|
import { testKbContext, TEST_BRANCH_MODEL } from '../../../__tests__/kb-context.js';
|
|
10
10
|
import { NodeFs } from '../../kb-fs/node-fs.js';
|
|
11
|
-
import { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
12
11
|
import type { AuthService } from '../../auth/auth.service.js';
|
|
13
12
|
import type { ICreatorAccess } from '../../access-model/creator.js';
|
|
14
13
|
import type { WorkflowEventBus } from '../../workflow/event-bus.js';
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
type BranchDeclaration,
|
|
25
24
|
type ToolDefSpec,
|
|
26
25
|
} from '../index.js';
|
|
26
|
+
import { newWorkspaceService } from '../../../__tests__/workspace-service.js';
|
|
27
27
|
|
|
28
28
|
const execFileAsync = promisify(execFile);
|
|
29
29
|
const KB_DIR = 'knowledge-base';
|
|
@@ -116,7 +116,7 @@ describe('a tool declares how it treats its branch', () => {
|
|
|
116
116
|
await runGit(seed, ['push', 'origin', 'HEAD:refs/heads/refs/heads/ghost']);
|
|
117
117
|
|
|
118
118
|
kb = testKbContext({ kbDirName: KB_DIR });
|
|
119
|
-
const workspaces =
|
|
119
|
+
const workspaces = newWorkspaceService(workspacesRoot, upstream, kb, new NodeFs());
|
|
120
120
|
const resolve = createToolContextResolver({
|
|
121
121
|
authService: {
|
|
122
122
|
getUserById: async () => ({ id: 'u', email: 'alice@example.com', name: 'Alice' }),
|