@bevel-software/platform-core-backend 0.34.0 → 0.35.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 +146 -300
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +16 -11
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +132 -154
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/create-core-server.js +1 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +6 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +7 -4
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +22 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +3 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +75 -0
- 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 +23 -4
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +45 -31
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +4 -0
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +10 -8
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +6 -35
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +85 -39
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/skills.tools.js +1 -1
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +27 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +12 -8
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +115 -44
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +31 -10
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +22 -18
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +71 -21
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/create-core-server.ts +1 -1
- package/src/core/create-core-services.ts +13 -3
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -12
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/access-control.interface.ts +26 -0
- package/src/modules/access/access-control.service.ts +92 -0
- package/src/modules/access/access.routes.ts +23 -4
- package/src/modules/access/index.ts +1 -1
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +79 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +49 -31
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +4 -4
- package/src/modules/agent-instructions/compose.ts +4 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +10 -8
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/mcp/mcp.service.ts +5 -35
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +91 -39
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/skills.tools.ts +1 -1
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +96 -5
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +22 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +138 -46
- package/src/modules/tool-manuals/tool-manuals.tools.ts +53 -14
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/workflow/agent-tools/workflow.tools.ts +22 -18
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +8 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +156 -49
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/workspace.tools.ts +74 -21
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/read-capped.ts +40 -0
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
|
@@ -142,6 +142,12 @@ async function keepFolderOf(fs, ctx, branch, removedPath, kbDirName) {
|
|
|
142
142
|
}
|
|
143
143
|
const int = (description) => ({ type: 'integer', description });
|
|
144
144
|
const str = (description) => ({ type: 'string', description });
|
|
145
|
+
/**
|
|
146
|
+
* A write to the default branch is live at once, so the agent asks first. Said on the four
|
|
147
|
+
* tools behind 87 of the 91 writes there in the agent-guide benchmark (2026-10-26); `start-here`
|
|
148
|
+
* says it for the other write tools.
|
|
149
|
+
*/
|
|
150
|
+
const ASK_BEFORE_DEFAULT_BRANCH = ' On the default branch a write is live for everyone at once: unless the person already said where, ask them first whether to write there or propose it on a draft.';
|
|
145
151
|
/**
|
|
146
152
|
* The upload route, named on every tool that takes content as a JSON string.
|
|
147
153
|
*
|
|
@@ -829,8 +835,9 @@ changeGate,
|
|
|
829
835
|
*/
|
|
830
836
|
uploads,
|
|
831
837
|
/**
|
|
832
|
-
* The platform's agent guide
|
|
833
|
-
*
|
|
838
|
+
* The start of the platform's agent guide and the list of its sections
|
|
839
|
+
* (`guideStartText` in `modules/agent-guide`), which `read_file` and
|
|
840
|
+
* `file_stat` answer at the guide's name in the repository root — after
|
|
834
841
|
* the knowledge base's own file of that name, when it has one. Optional for
|
|
835
842
|
* the harnesses that are about the file primitives; without it the two
|
|
836
843
|
* tools read the disk and nothing else.
|
|
@@ -1636,13 +1643,13 @@ patternMatcher) {
|
|
|
1636
1643
|
// transport hiccup on its first call as an unrecoverable start.
|
|
1637
1644
|
const startSessionDef = toolDef({
|
|
1638
1645
|
name: 'start_session',
|
|
1639
|
-
description: 'Mint this conversation\'s id: the `sessionId` KnowledgeBase tools take. Call this ONCE, at the start of your work — minting a new id mid-run starts a second conversation as far as the server is concerned.
|
|
1646
|
+
description: 'Mint this conversation\'s id: the `sessionId` KnowledgeBase tools take. Call this ONCE, at the start of your work — minting a new id mid-run starts a second conversation as far as the server is concerned. Pass the returned id as `sessionId` to every tool whose arguments list it, inside `call_tool_chain` too; the others refuse it. Where the deployment offers an `ask` tool, hand it the SAME id: your reads and your questions are then one conversation. RETRYING IS SAFE: a call that fails created nothing, so retry it. If a retry lands after a success you hold two independent ids, which is harmless: keep passing the one you already used and ignore the other. Returns `{ sessionId }`, plus `firstRun` while the knowledge base is still empty: a note to act on.',
|
|
1640
1647
|
path: '/api/agent/tools/start_session',
|
|
1641
1648
|
inputs: { type: 'object', properties: {}, additionalProperties: false },
|
|
1642
1649
|
outputs: {
|
|
1643
1650
|
type: 'object',
|
|
1644
1651
|
properties: {
|
|
1645
|
-
sessionId: str('The minted session id — pass it as `sessionId`
|
|
1652
|
+
sessionId: str('The minted session id — pass it as `sessionId` to the tools that take one, and to `ask` where there is one.'),
|
|
1646
1653
|
firstRun: str(`Present only while the knowledge base holds nothing but its starter guide (and a starter pack's untouched pages): what to offer the person (the guide's \`${FIRST_RUN_SECTION_ID}\` section says how).`),
|
|
1647
1654
|
},
|
|
1648
1655
|
required: ['sessionId'],
|
|
@@ -1843,7 +1850,7 @@ patternMatcher) {
|
|
|
1843
1850
|
description: 'Read a workspace file as text. Returns `{ path, content, canWrite }`. Per the shared content rule: text as ' +
|
|
1844
1851
|
'text, documents and email as extracted text, an image as the picture, anything else as a one-line ' +
|
|
1845
1852
|
'description. Also reads a `__tool_chain_spill__/…` ref back from a truncated `call_tool_chain`: it ' +
|
|
1846
|
-
'belongs to no workspace
|
|
1853
|
+
'belongs to no workspace: any existing `branch` will do, and no `canWrite` comes back. `offset`/`limit` slice the ' +
|
|
1847
1854
|
'content (characters; bytes for a spill ref; ignored for an image): page through large files and ' +
|
|
1848
1855
|
'spills, never read multi-MB whole. `canWrite`: may you change this file directly on the default branch. ' +
|
|
1849
1856
|
CAN_WRITE_CLAUSE,
|
|
@@ -1852,8 +1859,8 @@ patternMatcher) {
|
|
|
1852
1859
|
properties: {
|
|
1853
1860
|
branch: BRANCH_INPUT,
|
|
1854
1861
|
path: str(`Path to read, under \`${kbDirName}/\` (e.g. \`${kbDirName}/KnowledgeBase/Foo.md\`), with or without a leading slash — a path without that prefix is placed under \`${kbDirName}/\` — or a \`__tool_chain_spill__/…\` ref from a truncated \`call_tool_chain\`, or a \`__tmp__/…\` ref to a file you staged in the workspace's tmp folder.`),
|
|
1855
|
-
offset: int('Start character index (default 0).'),
|
|
1856
|
-
limit: int('Max characters to return from `offset
|
|
1862
|
+
offset: int('Start character index (bytes for a spill ref; default 0).'),
|
|
1863
|
+
limit: int('Max characters to return from `offset` (bytes for a spill ref).'),
|
|
1857
1864
|
sessionId: SESSION_ID_INPUT,
|
|
1858
1865
|
},
|
|
1859
1866
|
required: ['branch', 'path'],
|
|
@@ -2515,6 +2522,7 @@ patternMatcher) {
|
|
|
2515
2522
|
gated: true,
|
|
2516
2523
|
description: 'Write a workspace TEXT file. The change is committed + pushed as you. Returns `{ path, bytes, outcome }`, where `outcome` is ' +
|
|
2517
2524
|
'`created`, `replaced` or `updated`.' +
|
|
2525
|
+
ASK_BEFORE_DEFAULT_BRANCH +
|
|
2518
2526
|
UPLOAD_ROUTE_NOTE,
|
|
2519
2527
|
inputs: {
|
|
2520
2528
|
type: 'object',
|
|
@@ -2595,6 +2603,7 @@ patternMatcher) {
|
|
|
2595
2603
|
'`created` / `replaced` / `updated` for a path it wrote, or `refused` with `error` (the code) and `message` (why) for a ' +
|
|
2596
2604
|
'path it could not. `count` is how many were written. A path it refuses — the mode said no, or the file is not text — ' +
|
|
2597
2605
|
'does not stop the others; read `files` to see what landed.' +
|
|
2606
|
+
ASK_BEFORE_DEFAULT_BRANCH +
|
|
2598
2607
|
UPLOAD_ROUTE_NOTE,
|
|
2599
2608
|
inputs: {
|
|
2600
2609
|
type: 'object',
|
|
@@ -2632,7 +2641,7 @@ patternMatcher) {
|
|
|
2632
2641
|
enum: ['created', 'replaced', 'updated', 'refused'],
|
|
2633
2642
|
description: 'What happened at this path. `refused` means nothing was written there and the file is untouched.',
|
|
2634
2643
|
},
|
|
2635
|
-
error: str('Present when `outcome` is `refused`: the refusal code
|
|
2644
|
+
error: str('Present when `outcome` is `refused`: the refusal code, such as `exists`, `missing`, `binary_not_writable` or a code of the deployment\'s own write check.'),
|
|
2636
2645
|
message: str('Present when `outcome` is `refused`: the full refusal, the same one write_file would have given.'),
|
|
2637
2646
|
},
|
|
2638
2647
|
required: ['path', 'outcome'],
|
|
@@ -2761,6 +2770,7 @@ patternMatcher) {
|
|
|
2761
2770
|
name: 'edit_file',
|
|
2762
2771
|
gated: true,
|
|
2763
2772
|
description: 'Replace an exact string in a workspace TEXT file. `old_string` must appear exactly once unless `replace_all`. Committed + pushed as you.' +
|
|
2773
|
+
ASK_BEFORE_DEFAULT_BRANCH +
|
|
2764
2774
|
UPLOAD_ROUTE_NOTE,
|
|
2765
2775
|
inputs: {
|
|
2766
2776
|
type: 'object',
|
|
@@ -2839,7 +2849,8 @@ patternMatcher) {
|
|
|
2839
2849
|
mount({
|
|
2840
2850
|
name: 'delete_file',
|
|
2841
2851
|
gated: true,
|
|
2842
|
-
description: 'Delete ONE workspace file. Committed + pushed as you. Its folder stays, even when this was its last file. Files only: a folder is refused with a pointer to `delete_folder`.'
|
|
2852
|
+
description: 'Delete ONE workspace file. Committed + pushed as you. Its folder stays, even when this was its last file. Files only: a folder is refused with a pointer to `delete_folder`.' +
|
|
2853
|
+
ASK_BEFORE_DEFAULT_BRANCH,
|
|
2843
2854
|
inputs: {
|
|
2844
2855
|
type: 'object',
|
|
2845
2856
|
properties: {
|
|
@@ -3061,10 +3072,11 @@ patternMatcher) {
|
|
|
3061
3072
|
// the shared rules now, which are rebuilt from the layout where they live.
|
|
3062
3073
|
description: 'Move or rename a workspace FILE or FOLDER; a folder moves recursively, with everything under it. `dest` is the full new path, not the folder to move into. Committed + pushed as you. ' +
|
|
3063
3074
|
'The destination must not exist — a move never overwrites a file or merges into a folder. Access follows the ' +
|
|
3064
|
-
'DESTINATION folder, so a move can change
|
|
3065
|
-
'
|
|
3066
|
-
'
|
|
3067
|
-
'
|
|
3075
|
+
'DESTINATION folder, so a move can change who may open or edit the file. You must be able to read what moves and the folder it lands in, ' +
|
|
3076
|
+
'or it is refused as read_file refuses. The dry run answers ' +
|
|
3077
|
+
'`{ src, dest, kind, descendants, access: { before, after }, accessChanges, holderChanges?, allowed, reason? }`: your own ' +
|
|
3078
|
+
'access at both ends, and who else loses or gains it. A move that changes either wants `confirm: true`; tell the person ' +
|
|
3079
|
+
'who loses and who gains before you confirm. ' +
|
|
3068
3080
|
'Links are rewritten by default, in one commit with the move: those in the moved files and those in other markdown files pointing at them (`links` reports them). `rewriteLinks: false` turns that off. Path mentions in plain prose or code are never changed.',
|
|
3069
3081
|
inputs: {
|
|
3070
3082
|
type: 'object',
|
|
@@ -3073,7 +3085,7 @@ patternMatcher) {
|
|
|
3073
3085
|
src: wsPath(kbDirName, 'Source path (file or folder)'),
|
|
3074
3086
|
dest: wsPath(kbDirName, 'Destination path — the full new path; must not exist yet'),
|
|
3075
3087
|
dryRun: { type: 'boolean', description: 'Answer with the impact and change nothing.' },
|
|
3076
|
-
confirm: { type: 'boolean', description: 'Required when the move changes your access. Set it only after a dry run.' },
|
|
3088
|
+
confirm: { type: 'boolean', description: 'Required when the move changes your access or anyone else\'s. Set it only after a dry run.' },
|
|
3077
3089
|
rewriteLinks: { type: 'boolean', description: 'Rewrite the links into, out of and between the moved files (default true). `false` moves without touching any link.' },
|
|
3078
3090
|
sessionId: SESSION_ID_INPUT,
|
|
3079
3091
|
},
|
|
@@ -3087,8 +3099,13 @@ patternMatcher) {
|
|
|
3087
3099
|
dest: str('Destination path (echoes the input).'),
|
|
3088
3100
|
kind: str('`file` or `folder`.'),
|
|
3089
3101
|
descendants: int('Files that move: 1 for a file, the file count under a folder.'),
|
|
3090
|
-
access: { type: 'object', description: 'Your `{ read, write, download, owner }` at the source (`before`) and at the destination once the move has landed (`after`).' },
|
|
3102
|
+
access: { type: 'object', description: 'Your `{ read, write, download, owner }` at the source (`before`) and at the destination once the move has landed (`after`), with every `access.md` inside a moved folder counted at its new place.' },
|
|
3091
3103
|
accessChanges: { type: 'boolean', description: 'True when any of your verdicts differs between `before` and `after`.' },
|
|
3104
|
+
holderChanges: {
|
|
3105
|
+
type: 'object',
|
|
3106
|
+
description: 'Who loses and who gains opening (`read`) or editing (`write`) it once the move has landed: `{ lose, gain }`, each ' +
|
|
3107
|
+
'`[{ kind, name, email?, verb }]` with `kind` `group`, `role`, `plugin` or `person`. Absent when nobody\'s access changes.',
|
|
3108
|
+
},
|
|
3092
3109
|
allowed: { type: 'boolean', description: 'Whether the move may run.' },
|
|
3093
3110
|
reason: str('Why it may not, when `allowed` is false.'),
|
|
3094
3111
|
dryRun: { type: 'boolean', description: 'True on a dry run.' },
|
|
@@ -3117,6 +3134,23 @@ patternMatcher) {
|
|
|
3117
3134
|
// source already deleted.
|
|
3118
3135
|
await assertAgentWriteAllowed(agentAccessGate, ctx, branch, src);
|
|
3119
3136
|
await assertAgentWriteAllowed(agentAccessGate, ctx, branch, dest);
|
|
3137
|
+
// What moves and the folder it lands in must both be readable to the
|
|
3138
|
+
// caller, refused as read_file refuses them, on every branch: a move
|
|
3139
|
+
// preview reads both ends (the caller's own verdicts there, who else is
|
|
3140
|
+
// affected), and the folder's rules are what the names are made of. The
|
|
3141
|
+
// verdict comes BEFORE every other look, so neither end's existence is
|
|
3142
|
+
// told to someone who may not know it. The deployment's read hook is
|
|
3143
|
+
// asked first, as read_file asks it, and its refusal is the answer: a
|
|
3144
|
+
// move the hook would not let read is not previewed at all, since even
|
|
3145
|
+
// "this move asks for confirm" would tell what the comparison found. A
|
|
3146
|
+
// folder the caller may read moves with everything in it, files they
|
|
3147
|
+
// may not read included: those travel, they are not read.
|
|
3148
|
+
const destFolder = dest.slice(0, dest.lastIndexOf('/'));
|
|
3149
|
+
await notifyAgentRead(agentAccessGate, ctx, branch, src);
|
|
3150
|
+
await notifyAgentRead(agentAccessGate, ctx, branch, destFolder);
|
|
3151
|
+
const gate = readGateFor(branch, ctx);
|
|
3152
|
+
await assertCanRead(gate, src);
|
|
3153
|
+
await assertCanRead(gate, destFolder);
|
|
3120
3154
|
const fs = await ctx.getFilesystem(branch);
|
|
3121
3155
|
const root = await workspaceRoot(branch, ctx);
|
|
3122
3156
|
// Before the kind check, which stats THROUGH a link: a dangling link at
|
|
@@ -3140,6 +3174,17 @@ patternMatcher) {
|
|
|
3140
3174
|
accessAfter(branch, ctx, src, dest),
|
|
3141
3175
|
]);
|
|
3142
3176
|
const accessChanges = verbsDiffer(before, after);
|
|
3177
|
+
// Who ELSE gains or loses opening or editing it: the lists the app's move
|
|
3178
|
+
// confirmation shows people. The caller may read both ends (the hook and
|
|
3179
|
+
// the rules, gated above, as the app's route gates), so the names go to
|
|
3180
|
+
// them, and a change asks for `confirm`. Outside the repository no rules
|
|
3181
|
+
// travel.
|
|
3182
|
+
const fromRel = toKbRelative(src, kbDirName);
|
|
3183
|
+
const toRel = toKbRelative(dest, kbDirName);
|
|
3184
|
+
const changes = fromRel !== null && toRel !== null
|
|
3185
|
+
? await accessControl.prospectiveHolderChanges(workspaceIdForBranch(branch), fromRel, toRel)
|
|
3186
|
+
: undefined;
|
|
3187
|
+
const othersChange = changes !== undefined && (changes.lose.length > 0 || changes.gain.length > 0);
|
|
3143
3188
|
// The placeholder moves with its folder, but it is never content.
|
|
3144
3189
|
const descendants = srcFiles.filter((f) => !isFolderPlaceholder(f)).length;
|
|
3145
3190
|
// Neither end may be the platform's own: a move neither takes a platform
|
|
@@ -3243,6 +3288,7 @@ patternMatcher) {
|
|
|
3243
3288
|
descendants,
|
|
3244
3289
|
access: { before, after },
|
|
3245
3290
|
accessChanges,
|
|
3291
|
+
...(othersChange ? { holderChanges: changes } : {}),
|
|
3246
3292
|
allowed: moveReason === undefined,
|
|
3247
3293
|
...(moveReason !== undefined ? { reason: moveReason } : {}),
|
|
3248
3294
|
...(linkPlan ? { links: linkPlan.report } : {}),
|
|
@@ -3262,12 +3308,16 @@ patternMatcher) {
|
|
|
3262
3308
|
throw new ToolError(reason, 409);
|
|
3263
3309
|
if (linkPlan?.overCap !== undefined)
|
|
3264
3310
|
throw new ToolError(linkPlan.overCap, 400);
|
|
3265
|
-
if (accessChanges && a.confirm !== true) {
|
|
3311
|
+
if ((accessChanges || othersChange) && a.confirm !== true) {
|
|
3312
|
+
const why = [
|
|
3313
|
+
...(accessChanges ? [`your access at "${dest}" differs from "${src}"`] : []),
|
|
3314
|
+
...(othersChange ? ['it changes who else may open or edit it (`holderChanges`)'] : []),
|
|
3315
|
+
];
|
|
3266
3316
|
return {
|
|
3267
3317
|
...impact,
|
|
3268
3318
|
confirmationRequired: true,
|
|
3269
3319
|
moved: false,
|
|
3270
|
-
message: `Nothing was moved:
|
|
3320
|
+
message: `Nothing was moved: ${why.join(', and ')}, so this move requires confirm: true.`,
|
|
3271
3321
|
};
|
|
3272
3322
|
}
|
|
3273
3323
|
// The look above is what produces the sentence; the filesystem's own
|
|
@@ -3320,8 +3370,8 @@ patternMatcher) {
|
|
|
3320
3370
|
name: 'copy_file',
|
|
3321
3371
|
gated: true,
|
|
3322
3372
|
description: 'Copy a workspace FILE to a new path. The destination must not exist — like a move, a copy never overwrites a file or a folder; to change what is in a file that already exists, write it. Committed + pushed as you. `src` may be a `__tmp__/<path>` ref to a file you staged in the workspace\'s tmp folder: the copy is how such a file is kept, as a normal repository file. ' +
|
|
3323
|
-
'Preflight first: `dryRun: true` changes nothing and answers `{ src, dest, kind, descendants, access: { before, after }, accessChanges, allowed, reason? }` — `access` is your own `{ read, write, download, owner }` at the source and at the destination AS IT WILL BE once the copy has landed, with
|
|
3324
|
-
'One exception to that shape: when the destination is one you may not write, the answer is the refusal
|
|
3373
|
+
'Preflight first: `dryRun: true` changes nothing and answers `{ src, dest, kind, descendants, access: { before, after }, accessChanges, allowed, reason? }` — `access` is your own `{ read, write, download, owner }` at the source and at the destination AS IT WILL BE once the copy has landed, with the copy\'s own rules counted at its new place. ' +
|
|
3374
|
+
'One exception to that shape: when the destination is one you may not write, the answer is the refusal (`allowed: false` with `reason`, and the access figures) with no `kind` and no `descendants`, because nothing about the source is read before that verdict.',
|
|
3325
3375
|
inputs: {
|
|
3326
3376
|
type: 'object',
|
|
3327
3377
|
properties: {
|
|
@@ -3741,7 +3791,7 @@ patternMatcher) {
|
|
|
3741
3791
|
'`replaced` / `updated`, or `refused` with `error` (the code) and `message` (why). `count` is how many landed and ' +
|
|
3742
3792
|
'`total` how many paths there were; `files` is cut to the first 25 unless you pass `all: true`. ' +
|
|
3743
3793
|
'Every path is judged one by one — by your write access, the platform-file rules and what is already there — ' +
|
|
3744
|
-
'
|
|
3794
|
+
'as `write_file` judges it, except that platform files are refused and binary content is fine; a refused path does not stop the others. ' +
|
|
3745
3795
|
'The token is single-use: it is spent by the apply that lands it, and refused if you use it twice, let it ' +
|
|
3746
3796
|
'expire, or present one issued to somebody else. `mode` means what it means on `write_file`.',
|
|
3747
3797
|
inputs: {
|
|
@@ -3820,7 +3870,7 @@ patternMatcher) {
|
|
|
3820
3870
|
'each one inside a folder too, is included only if you may read AND download that file. Returns ' +
|
|
3821
3871
|
'`{ expiresAt, expiresInSeconds, files: [{ path, bytes, sha256, downloadUrl }], folders: [{ path, bytes, ' +
|
|
3822
3872
|
'downloadUrl, files }], refused: [{ path, reason }] }`: a link per file, with its own content type, and per ' +
|
|
3823
|
-
`folder a zip at full repository paths (\`apply_file_upload\` it at \`${kbDirName}/\` to put
|
|
3873
|
+
`folder a zip at full repository paths (\`apply_file_upload\` it at \`${kbDirName}/\` to put them back). ` +
|
|
3824
3874
|
'Fetch with any HTTP client (`curl -o <name> "<downloadUrl>"`, or the address without its last segment and an ' +
|
|
3825
3875
|
'`x-download-token` header). Each link works ONCE, for 15 minutes, and serves the files as they are now. ' +
|
|
3826
3876
|
'Refused: `not found` (missing or named-but-unreadable; folders omit unreadable files), ' +
|