@bevel-software/platform-core-backend 0.25.2 → 0.26.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 +234 -0
- package/agent-guide/conventions.md +27 -0
- package/agent-guide/directory-structure.md +145 -0
- package/agent-guide/finding-things.md +7 -0
- package/agent-guide/introduction.md +27 -0
- package/agent-guide/skills.md +47 -0
- package/agent-guide/tool-manuals.md +217 -0
- package/agent-guide/where-a-new-file-goes.md +36 -0
- package/dist/assets.d.ts +7 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +9 -0
- package/dist/assets.js.map +1 -1
- package/dist/core/core-ports.d.ts +11 -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 +13 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +9 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +14 -4
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +9 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -0
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +16 -0
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +139 -0
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.js +191 -0
- package/dist/modules/agent-guide/agent-guide.js.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +24 -0
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -0
- package/dist/modules/agent-guide/agent-guide.tools.js +100 -0
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +4 -0
- package/dist/modules/agent-guide/index.d.ts.map +1 -0
- package/dist/modules/agent-guide/index.js +4 -0
- package/dist/modules/agent-guide/index.js.map +1 -0
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +3 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +9 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +9 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +1 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +1 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +10 -50
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +32 -85
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +29 -2
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +113 -16
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/tool-schema-guard.d.ts +105 -0
- package/dist/modules/mcp/tool-schema-guard.d.ts.map +1 -0
- package/dist/modules/mcp/tool-schema-guard.js +171 -0
- package/dist/modules/mcp/tool-schema-guard.js.map +1 -0
- package/dist/modules/plugins/plugins.tools.d.ts +36 -2
- package/dist/modules/plugins/plugins.tools.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.tools.js +71 -14
- package/dist/modules/plugins/plugins.tools.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +0 -7
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +14 -53
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +3 -6
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +58 -16
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +23 -4
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +4 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +14 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +7 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -36
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +14 -14
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -1
- package/dist/modules/tool-registry/description-length.js +24 -26
- package/dist/modules/tool-registry/description-length.js.map +1 -1
- package/dist/modules/tool-registry/guide-first.d.ts +23 -0
- package/dist/modules/tool-registry/guide-first.d.ts.map +1 -0
- package/dist/modules/tool-registry/guide-first.js +32 -0
- package/dist/modules/tool-registry/guide-first.js.map +1 -0
- package/dist/modules/tool-registry/tool-registry.d.ts +6 -0
- package/dist/modules/tool-registry/tool-registry.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool-registry.js +9 -2
- package/dist/modules/tool-registry/tool-registry.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts +449 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js +481 -0
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts +73 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +582 -0
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +12 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js +5 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.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 +9 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +210 -13
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +456 -91
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/merge-commit.d.ts +73 -0
- package/dist/modules/workflow/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/workflow/git/merge-commit.js +89 -0
- package/dist/modules/workflow/git/merge-commit.js.map +1 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts +94 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +332 -37
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts +35 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +178 -12
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts +6 -2
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +7 -2
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +4 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +22 -27
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +58 -52
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +209 -223
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +5 -3
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +5 -3
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +10 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +211 -18
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +11 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +14 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/hidden-tools.d.ts +44 -0
- package/dist/shared/hidden-tools.d.ts.map +1 -0
- package/dist/shared/hidden-tools.js +13 -0
- package/dist/shared/hidden-tools.js.map +1 -0
- package/kb-template/.bevelignore +0 -5
- package/package.json +4 -3
- package/src/__tests__/kb-layout-config.test.ts +10 -100
- package/src/__tests__/packaged-assets-ship.test.ts +54 -0
- package/src/assets.ts +10 -0
- package/src/core/core-ports.ts +11 -0
- package/src/core/create-core-server.ts +13 -2
- package/src/core/create-core-services.ts +28 -4
- package/src/index.ts +2 -2
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +58 -0
- package/src/modules/access/__tests__/access-control.platform-restore.test.ts +8 -7
- package/src/modules/access/__tests__/access-personal-plugin.test.ts +1 -18
- package/src/modules/access/access-control.interface.ts +15 -0
- package/src/modules/access/access-control.service.ts +21 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +328 -0
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +189 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +122 -0
- package/src/modules/agent-guide/agent-guide.ts +291 -0
- package/src/modules/agent-guide/index.ts +21 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +28 -121
- package/src/modules/agent-instructions/agent-instructions.routes.ts +3 -2
- package/src/modules/agent-instructions/compose.ts +9 -6
- package/src/modules/agent-instructions/index.ts +0 -3
- package/src/modules/agent-instructions/shared-file-rules.ts +31 -93
- package/src/modules/mcp/__tests__/fake-downstream-mcp-server.ts +14 -3
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +250 -0
- package/src/modules/mcp/__tests__/mcp.service.test.ts +31 -23
- package/src/modules/mcp/__tests__/tool-schema-guard.test.ts +266 -0
- package/src/modules/mcp/mcp.service.ts +137 -19
- package/src/modules/mcp/tool-schema-guard.ts +196 -0
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +154 -4
- package/src/modules/plugins/plugins.tools.ts +75 -15
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +26 -55
- package/src/modules/settings/deployment-settings.service.ts +13 -54
- package/src/modules/settings/setup.routes.ts +3 -6
- package/src/modules/skills/__tests__/skills.tools.description.test.ts +91 -0
- package/src/modules/skills/skills.tools.ts +62 -16
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +57 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +73 -4
- package/src/modules/tool-manuals/tool-manuals.contract.ts +24 -4
- package/src/modules/tool-manuals/tool-manuals.service.ts +17 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +74 -36
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +160 -0
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +61 -59
- package/src/modules/tool-registry/description-length.ts +24 -26
- package/src/modules/tool-registry/guide-first.ts +34 -0
- package/src/modules/tool-registry/tool-registry.ts +9 -2
- package/src/modules/workflow/__tests__/apply-failure.test.ts +6 -1
- package/src/modules/workflow/agent-tools/__tests__/change-request-read-shape.test.ts +705 -0
- package/src/modules/workflow/agent-tools/__tests__/change-request-read.tools.test.ts +1518 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +23 -2
- package/src/modules/workflow/agent-tools/change-request-read-shape.ts +712 -0
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +724 -0
- package/src/modules/workflow/agent-tools/change-request-summary.ts +5 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +8 -0
- package/src/modules/workflow/git/__tests__/git.service.appliedChange.test.ts +285 -0
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +124 -0
- package/src/modules/workflow/git/__tests__/git.service.mergeChangeRequest.test.ts +334 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.list-fetch.test.ts +72 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.placeholder.test.ts +24 -2
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +620 -1
- package/src/modules/workflow/git/git.service.ts +537 -94
- package/src/modules/workflow/git/merge-commit.ts +88 -0
- package/src/modules/workflow/git/pull-request.service.ts +380 -54
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +7 -1
- package/src/modules/workflow/review-workflow/__tests__/merge-records-own-commit.test.ts +407 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +189 -11
- package/src/modules/workflow/workflow.routes.ts +7 -2
- package/src/modules/workflow/workflow.service.ts +7 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +21 -10
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +33 -55
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +255 -22
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +191 -489
- package/src/modules/workspace/startup/steps/seed-tree.ts +21 -27
- package/src/modules/workspace/startup/steps/template-files.step.ts +217 -249
- package/src/modules/workspace/startup/steps/template-source.ts +5 -3
- package/src/modules/workspace/workspace.tools.ts +226 -16
- package/src/shared/domain-errors.ts +15 -0
- package/src/shared/hidden-tools.ts +45 -0
- package/kb-template/AGENTS.md +0 -730
|
@@ -40,9 +40,11 @@ export const TEMPLATE_SOURCE_FALLBACKS: ReadonlyMap<string, string> = new Map([
|
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Where
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* Where a template file may ask for the rules every file tool shares. The
|
|
44
|
+
* packaged template no longer does — the guide is composed in code, see
|
|
45
|
+
* `modules/agent-guide` — but a distribution's template may. Not a layout
|
|
46
|
+
* placeholder: the layout renderer lives in `platform-shared`, which the
|
|
47
|
+
* frontend also reads, and the rules are backend text.
|
|
46
48
|
*/
|
|
47
49
|
export const SHARED_FILE_RULES_PLACEHOLDER = '{{sharedFileRules}}';
|
|
48
50
|
|
|
@@ -58,9 +58,15 @@ import {
|
|
|
58
58
|
} from '@bevel-software/platform-shared';
|
|
59
59
|
import type { KbContext } from '../../shared/kb-context.js';
|
|
60
60
|
import { AccessDeniedError } from '../access-model/access-errors.js';
|
|
61
|
+
import {
|
|
62
|
+
AGENT_GUIDE_FILE,
|
|
63
|
+
isAgentGuidePath,
|
|
64
|
+
isManagedGuide,
|
|
65
|
+
withPlatformGuideAppended,
|
|
66
|
+
type AgentGuideReader,
|
|
67
|
+
} from '../agent-guide/agent-guide.js';
|
|
61
68
|
import { removeEmptyDirs } from './empty-dirs.js';
|
|
62
69
|
import { rethrowAsWriteDenial } from './write-denial.js';
|
|
63
|
-
import { sharedRulesPointer } from '../agent-instructions/shared-file-rules.js';
|
|
64
70
|
import type { IChangeReadGate } from '../access-model/change-gate.js';
|
|
65
71
|
import { notFound, orDeclaredNotFound, orNotFound } from './not-found.js';
|
|
66
72
|
import { logger } from '../../shared/logging.js';
|
|
@@ -730,6 +736,8 @@ async function grepWalk(
|
|
|
730
736
|
gate: ReadGate,
|
|
731
737
|
notifyRead: (path: string) => Promise<void>,
|
|
732
738
|
docs: DocGrepState,
|
|
739
|
+
/** A file the walk leaves to its caller: never opened, never counted against `max`. */
|
|
740
|
+
skip: (path: string) => boolean = () => false,
|
|
733
741
|
): Promise<void> {
|
|
734
742
|
if (out.length >= max || depth > 12) return;
|
|
735
743
|
let entries;
|
|
@@ -747,7 +755,9 @@ async function grepWalk(
|
|
|
747
755
|
if (e.type !== 'directory' && isFolderPlaceholder(e.name)) continue;
|
|
748
756
|
const p = dir ? `${dir}/${e.name}` : e.name;
|
|
749
757
|
if (e.type === 'directory') {
|
|
750
|
-
await grepWalk(fs, p, re, out, max, depth + 1, gate, notifyRead, docs);
|
|
758
|
+
await grepWalk(fs, p, re, out, max, depth + 1, gate, notifyRead, docs, skip);
|
|
759
|
+
} else if (skip(p)) {
|
|
760
|
+
continue;
|
|
751
761
|
} else {
|
|
752
762
|
// Opening a file is a read of it, even when the walk started at a root
|
|
753
763
|
// the read hook was already told about — so every file the walk opens
|
|
@@ -857,6 +867,14 @@ export function registerWorkspaceTools(
|
|
|
857
867
|
* two tools are not mounted at all rather than mounted and broken.
|
|
858
868
|
*/
|
|
859
869
|
uploads?: AgentUploadStore,
|
|
870
|
+
/**
|
|
871
|
+
* The platform's agent guide (see `modules/agent-guide`), which `read_file`
|
|
872
|
+
* and `file_stat` answer at the guide's name in the repository root — after
|
|
873
|
+
* the knowledge base's own file of that name, when it has one. Optional for
|
|
874
|
+
* the harnesses that are about the file primitives; without it the two
|
|
875
|
+
* tools read the disk and nothing else.
|
|
876
|
+
*/
|
|
877
|
+
agentGuide?: AgentGuideReader,
|
|
860
878
|
): void {
|
|
861
879
|
const { kbDirName } = kb;
|
|
862
880
|
/**
|
|
@@ -1490,8 +1508,7 @@ export function registerWorkspaceTools(
|
|
|
1490
1508
|
const requiresBranch = ((spec.inputs as { required?: string[] }).required ?? []).includes('branch');
|
|
1491
1509
|
const describe = (): string =>
|
|
1492
1510
|
(typeof spec.description === 'function' ? spec.description() : spec.description) +
|
|
1493
|
-
(spec.gated ? agentAccessGate.notes.gatedToolNote() : '')
|
|
1494
|
-
sharedRulesPointer(kb.layout);
|
|
1511
|
+
(spec.gated ? agentAccessGate.notes.gatedToolNote() : '');
|
|
1495
1512
|
const def = toolDef({
|
|
1496
1513
|
name: spec.name,
|
|
1497
1514
|
description: describe(),
|
|
@@ -1623,7 +1640,7 @@ export function registerWorkspaceTools(
|
|
|
1623
1640
|
const startSessionDef = toolDef({
|
|
1624
1641
|
name: 'start_session',
|
|
1625
1642
|
description:
|
|
1626
|
-
'Mint the id of this conversation, which the KnowledgeBase tools take as `sessionId`. Call this ONCE, at the start of your work
|
|
1643
|
+
'Mint the id of this conversation, which the KnowledgeBase tools take as `sessionId`. 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. The id is also a chat session in the app, so you can hand the SAME id to the `ask` tool: your reads and your questions are then one conversation. Pass the returned id as `sessionId` on every later KnowledgeBase tool call (direct MCP calls and inside `call_tool_chain` alike). 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 }`.',
|
|
1627
1644
|
path: '/api/agent/tools/start_session',
|
|
1628
1645
|
inputs: { type: 'object', properties: {}, additionalProperties: false },
|
|
1629
1646
|
outputs: {
|
|
@@ -1689,6 +1706,22 @@ export function registerWorkspaceTools(
|
|
|
1689
1706
|
if (spillStore.isSpillRef(p)) {
|
|
1690
1707
|
return { path: p, content: await spillStore.read(p, offset, limit) };
|
|
1691
1708
|
}
|
|
1709
|
+
const slice = (content: string): string => {
|
|
1710
|
+
const start = offset && offset > 0 ? offset : 0;
|
|
1711
|
+
return offset !== undefined || limit !== undefined
|
|
1712
|
+
? content.slice(start, limit !== undefined ? start + limit : undefined)
|
|
1713
|
+
: content;
|
|
1714
|
+
};
|
|
1715
|
+
// The guide's name at the repository root answers with the platform's
|
|
1716
|
+
// guide, which is text the code owns and every agent may read: no gate
|
|
1717
|
+
// and no read hook for it. A file the knowledge base keeps under that
|
|
1718
|
+
// name is ITS OWN conventions page and is read as any file is — gated,
|
|
1719
|
+
// noted — and comes first, with the guide after it. A copy of the
|
|
1720
|
+
// guide an earlier release wrote to disk (still on a draft, say) is
|
|
1721
|
+
// recognised by its header and not served a second time.
|
|
1722
|
+
if (agentGuide && isAgentGuidePath(toKbRelative(p, kbDirName) ?? '')) {
|
|
1723
|
+
return { path: p, content: slice(await guideAt(a.branch as string, ctx, p)) };
|
|
1724
|
+
}
|
|
1692
1725
|
await notifyAgentRead(agentAccessGate, ctx, a.branch as string, p);
|
|
1693
1726
|
await assertCanRead(readGateFor(a.branch as string, ctx), p);
|
|
1694
1727
|
const fs = await ctx.getFilesystem(a.branch as string);
|
|
@@ -1717,14 +1750,105 @@ export function registerWorkspaceTools(
|
|
|
1717
1750
|
// document, unreadable binary, oversized image) IS the file's honest
|
|
1718
1751
|
// textual answer, sliced like any other content.
|
|
1719
1752
|
const content = result.kind === 'text' ? result.text : result.message;
|
|
1720
|
-
|
|
1721
|
-
const sliced = offset !== undefined || limit !== undefined
|
|
1722
|
-
? content.slice(start, limit !== undefined ? start + limit : undefined)
|
|
1723
|
-
: content;
|
|
1724
|
-
return { path: p, content: sliced };
|
|
1753
|
+
return { path: p, content: slice(content) };
|
|
1725
1754
|
},
|
|
1726
1755
|
});
|
|
1727
1756
|
|
|
1757
|
+
/**
|
|
1758
|
+
* The knowledge base's OWN file at the guide's path, read as any file is —
|
|
1759
|
+
* through the read hook and the access gate — or null when there is none,
|
|
1760
|
+
* when the caller MAY NOT READ IT, or when what is there is a copy of the
|
|
1761
|
+
* platform's guide an earlier release wrote (recognised by its header),
|
|
1762
|
+
* which the guide served beside it would only repeat. A file that is not
|
|
1763
|
+
* text (a binary squatting the name) is read for what it is: its honest
|
|
1764
|
+
* textual answer.
|
|
1765
|
+
*
|
|
1766
|
+
* A file the caller may not read answers EXACTLY as no file does: the guide
|
|
1767
|
+
* alone, with nothing said. A refusal here would tell a caller the root
|
|
1768
|
+
* denies that a conventions file exists, which is the one thing the
|
|
1769
|
+
* platform never tells about a file someone may not read — a restricted
|
|
1770
|
+
* node is indistinguishable from an absent one on every other read.
|
|
1771
|
+
*/
|
|
1772
|
+
/** What a read of the guide's path answers: the guide, after the knowledge base's own readable file when it has one. */
|
|
1773
|
+
const guideAt = async (branch: string, ctx: ToolContext, p: string): Promise<string> => {
|
|
1774
|
+
const guide = await agentGuide!();
|
|
1775
|
+
const own = await ownGuideFile(branch, ctx, p);
|
|
1776
|
+
return own === null ? guide : withPlatformGuideAppended(own, guide);
|
|
1777
|
+
};
|
|
1778
|
+
|
|
1779
|
+
const ownGuideFile = async (branch: string, ctx: ToolContext, p: string): Promise<string | null> => {
|
|
1780
|
+
const fs = await ctx.getFilesystem(branch);
|
|
1781
|
+
// Existence first, then the gate, then the hook and the bytes — nothing of
|
|
1782
|
+
// theirs is read, or noted as read, before they are allowed to read it.
|
|
1783
|
+
if (!(await ownGuideReadable(fs, branch, ctx, p))) return null;
|
|
1784
|
+
await notifyAgentRead(agentAccessGate, ctx, branch, p);
|
|
1785
|
+
// Gone between the probe and the read — a concurrent delete — is the
|
|
1786
|
+
// absent case: the guide alone, as a read a moment later would answer.
|
|
1787
|
+
const bytes = await fs.readFile(p).then(asBytes, (err: unknown) => {
|
|
1788
|
+
if (isAbsence(err)) return null;
|
|
1789
|
+
throw err;
|
|
1790
|
+
});
|
|
1791
|
+
if (bytes === null) return null;
|
|
1792
|
+
const result = await readers.readerFor(p).read(bytes, p);
|
|
1793
|
+
const text = result.kind === 'text' ? result.text : result.kind === 'image' ? result.note : result.message;
|
|
1794
|
+
return isManagedGuide(text) ? null : text;
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Whether there is a file of the knowledge base's own at the guide's path
|
|
1799
|
+
* that THIS caller may read. False for nothing there and for a file the
|
|
1800
|
+
* access rules close to them, on purpose and without distinction (see
|
|
1801
|
+
* {@link ownGuideFile}).
|
|
1802
|
+
*/
|
|
1803
|
+
const ownGuideReadable = async (fs: LocalFilesystem, branch: string, ctx: ToolContext, p: string): Promise<boolean> => {
|
|
1804
|
+
// The permission verdict BEFORE the filesystem is asked anything, as on
|
|
1805
|
+
// every other read: a caller the rules close the path to learns nothing
|
|
1806
|
+
// from it — not that something is there, and not what the filesystem
|
|
1807
|
+
// says about an entry it cannot stat.
|
|
1808
|
+
const gate = readGateFor(branch, ctx);
|
|
1809
|
+
const rel = toKbRelative(p, gate.kbDirName);
|
|
1810
|
+
if (rel !== null && !(await gate.accessControl.canRead(gate.workspaceId, gate.userEmail, rel))) return false;
|
|
1811
|
+
return existsAt(fs, p);
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
/** Whether something is at `p` on `fs` — absence is false, any other failure is thrown. */
|
|
1815
|
+
const existsAt = async (fs: LocalFilesystem, p: string): Promise<boolean> =>
|
|
1816
|
+
fs.stat(p).then(
|
|
1817
|
+
() => true,
|
|
1818
|
+
(err: unknown) => {
|
|
1819
|
+
if (isAbsence(err)) return false;
|
|
1820
|
+
throw err;
|
|
1821
|
+
},
|
|
1822
|
+
);
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Whether what is at `p` is an entry of the knowledge base's own for the
|
|
1826
|
+
* ordinary stat to describe: anything there except a plain file that is a
|
|
1827
|
+
* copy of the guide an earlier release wrote (recognised by its header).
|
|
1828
|
+
* A folder at the guide's name is theirs and is never read — reading a
|
|
1829
|
+
* folder is an error, not an absence. Nothing there, at the stat or at the
|
|
1830
|
+
* read a moment later (a concurrent delete), is the absent case, which the
|
|
1831
|
+
* caller answers with the guide.
|
|
1832
|
+
*/
|
|
1833
|
+
const isOwnEntryStill = async (fs: LocalFilesystem, p: string): Promise<boolean> => {
|
|
1834
|
+
const type = await fs.stat(p).then(
|
|
1835
|
+
(st) => st.type,
|
|
1836
|
+
(err: unknown) => {
|
|
1837
|
+
if (isAbsence(err)) return undefined;
|
|
1838
|
+
throw err;
|
|
1839
|
+
},
|
|
1840
|
+
);
|
|
1841
|
+
if (type === undefined) return false;
|
|
1842
|
+
if (type !== 'file') return true;
|
|
1843
|
+
const bytes = await fs.readFile(p).then(asBytes, (err: unknown) => {
|
|
1844
|
+
if (isAbsence(err)) return null;
|
|
1845
|
+
throw err;
|
|
1846
|
+
});
|
|
1847
|
+
if (bytes === null) return false;
|
|
1848
|
+
const result = await readers.readerFor(p).read(bytes, p);
|
|
1849
|
+
return !(result.kind === 'text' && isManagedGuide(result.text));
|
|
1850
|
+
};
|
|
1851
|
+
|
|
1728
1852
|
mount({
|
|
1729
1853
|
name: 'list_files',
|
|
1730
1854
|
gated: true,
|
|
@@ -1849,6 +1973,11 @@ export function registerWorkspaceTools(
|
|
|
1849
1973
|
},
|
|
1850
1974
|
textEditable: { type: 'boolean', description: 'Files only: whether write_file/write_files/edit_file accept this file as it is now.' },
|
|
1851
1975
|
mimeNote: str('Present when `mimeSource` is `fallback`: says the MIME type is a fallback, not a detected type.'),
|
|
1976
|
+
platformGuide: {
|
|
1977
|
+
type: 'boolean',
|
|
1978
|
+
description:
|
|
1979
|
+
"True at the agent guide's name in the repository root when the knowledge base has no file of its own there: what read_file answers is the platform's guide, which is not on disk and cannot be written, moved or deleted.",
|
|
1980
|
+
},
|
|
1852
1981
|
},
|
|
1853
1982
|
required: ['managed', 'movable', 'deletable', 'access'],
|
|
1854
1983
|
additionalProperties: true,
|
|
@@ -1857,7 +1986,48 @@ export function registerWorkspaceTools(
|
|
|
1857
1986
|
handler: async (a, ctx: ToolContext) => {
|
|
1858
1987
|
const p = a.path as string;
|
|
1859
1988
|
const branch = a.branch as string;
|
|
1860
|
-
|
|
1989
|
+
// The guide's name with no file of the knowledge base's own under it —
|
|
1990
|
+
// or one the caller may not read, or a copy of the guide an earlier
|
|
1991
|
+
// release wrote, none of which `read_file` serves: what a read answers
|
|
1992
|
+
// there is the platform's guide, so stat says a text file is there to
|
|
1993
|
+
// read — ungated, like the read — and that nothing can be moved,
|
|
1994
|
+
// deleted or written at it through these tools. The three cases get
|
|
1995
|
+
// ONE answer on purpose: a different one for the file the caller may
|
|
1996
|
+
// not read would tell them it exists. A file of the knowledge base's
|
|
1997
|
+
// own that the caller may read is a file like any other, and the
|
|
1998
|
+
// ordinary answer below describes it.
|
|
1999
|
+
let noted = false;
|
|
2000
|
+
if (agentGuide && isAgentGuidePath(toKbRelative(p, kbDirName) ?? '')) {
|
|
2001
|
+
const fs = await ctx.getFilesystem(branch);
|
|
2002
|
+
const readable = await ownGuideReadable(fs, branch, ctx, p);
|
|
2003
|
+
// Telling the organisation's own file from a stale copy READS it, so
|
|
2004
|
+
// the read hook hears of it as it hears of a read_file there — after
|
|
2005
|
+
// the gate, never before, and once (the ordinary stat below is told).
|
|
2006
|
+
if (readable) {
|
|
2007
|
+
await notifyAgentRead(agentAccessGate, ctx, branch, p);
|
|
2008
|
+
noted = true;
|
|
2009
|
+
}
|
|
2010
|
+
const own = readable && (await isOwnEntryStill(fs, p));
|
|
2011
|
+
if (!own) {
|
|
2012
|
+
const guide = await agentGuide();
|
|
2013
|
+
return {
|
|
2014
|
+
name: p.slice(p.lastIndexOf('/') + 1),
|
|
2015
|
+
type: 'file',
|
|
2016
|
+
size: Buffer.byteLength(guide, 'utf8'),
|
|
2017
|
+
managed: true,
|
|
2018
|
+
movable: false,
|
|
2019
|
+
deletable: false,
|
|
2020
|
+
access: { read: true, write: false, download: false, owner: false },
|
|
2021
|
+
contentMode: 'text',
|
|
2022
|
+
kind: 'text',
|
|
2023
|
+
mime: 'text/markdown',
|
|
2024
|
+
mimeSource: 'extension',
|
|
2025
|
+
textEditable: false,
|
|
2026
|
+
platformGuide: true,
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (!noted) await notifyAgentRead(agentAccessGate, ctx, branch, p);
|
|
1861
2031
|
await assertCanRead(readGateFor(branch, ctx), p);
|
|
1862
2032
|
// Nothing there is a 404, and the placeholder — never content — gets
|
|
1863
2033
|
// exactly that answer: the one every file tool gives (see not-found.ts).
|
|
@@ -2010,10 +2180,6 @@ export function registerWorkspaceTools(
|
|
|
2010
2180
|
// (an empty path is the handler's to explain), and here it would
|
|
2011
2181
|
// otherwise name the workspace directory by another spelling.
|
|
2012
2182
|
const searchRoot = typeof a.path === 'string' && a.path.length > 0 ? a.path : kbDirName;
|
|
2013
|
-
// The search root itself goes to the read hook here; each file the walk
|
|
2014
|
-
// actually opens goes to it per-file below, so a hook sees every path a
|
|
2015
|
-
// grep reached rather than only the root it started from.
|
|
2016
|
-
await notifyAgentRead(agentAccessGate, ctx, a.branch as string, searchRoot);
|
|
2017
2183
|
const fs = await ctx.getFilesystem(a.branch as string);
|
|
2018
2184
|
const gate = readGateFor(a.branch as string, ctx);
|
|
2019
2185
|
const out: { path: string; line: number; text: string }[] = [];
|
|
@@ -2031,7 +2197,51 @@ export function registerWorkspaceTools(
|
|
|
2031
2197
|
: await searchRootKind(fs, searchRoot);
|
|
2032
2198
|
/** Why a single-file search found nothing, when "no matches" would be a lie. */
|
|
2033
2199
|
let fileNote: string | undefined;
|
|
2034
|
-
|
|
2200
|
+
// The guide is searched where it is read: a search of the repository
|
|
2201
|
+
// root covers it, and a search of its own path is a search of what
|
|
2202
|
+
// `read_file` answers there. The composed text is what is searched —
|
|
2203
|
+
// the knowledge base's own readable file first, then the platform's
|
|
2204
|
+
// guide — under the guide's path and with the line numbers a read of
|
|
2205
|
+
// it gives. The walk leaves that one file to this: a match the walk
|
|
2206
|
+
// made there would be the same line again, and one it COUNTED against
|
|
2207
|
+
// `max_results` would be a file later in the tree never searched while
|
|
2208
|
+
// the answer says nothing was cut. A file the caller may not read is
|
|
2209
|
+
// absent from it, as it is from the read.
|
|
2210
|
+
const guidePath = `${kbDirName}/${AGENT_GUIDE_FILE}`;
|
|
2211
|
+
const rel = toKbRelative(searchRoot, kbDirName);
|
|
2212
|
+
const coversGuide =
|
|
2213
|
+
agentGuide !== undefined && (kind === 'directory' ? rel === null : isAgentGuidePath(rel ?? ''));
|
|
2214
|
+
// The search root goes to the read hook — once, and only when it is
|
|
2215
|
+
// repository content: a folder, or a file of the knowledge base's own.
|
|
2216
|
+
// The guide's own path is not told here, because `guideAt` tells the
|
|
2217
|
+
// hook of the organisation's file there exactly as `read_file` does
|
|
2218
|
+
// (after the gate), and the platform's guide alone is nobody's file to
|
|
2219
|
+
// note. Each file the walk opens goes to the hook per file below, so a
|
|
2220
|
+
// hook sees every path a grep reached rather than only its root.
|
|
2221
|
+
if (!(coversGuide && kind !== 'directory')) {
|
|
2222
|
+
await notifyAgentRead(agentAccessGate, ctx, a.branch as string, searchRoot);
|
|
2223
|
+
}
|
|
2224
|
+
if (coversGuide) {
|
|
2225
|
+
if (kind === 'directory') {
|
|
2226
|
+
await grepWalk(
|
|
2227
|
+
fs,
|
|
2228
|
+
searchRoot,
|
|
2229
|
+
re,
|
|
2230
|
+
out,
|
|
2231
|
+
max,
|
|
2232
|
+
0,
|
|
2233
|
+
gate,
|
|
2234
|
+
(p) => notifyAgentRead(agentAccessGate, ctx, a.branch as string, p),
|
|
2235
|
+
docs,
|
|
2236
|
+
(p) => p === guidePath,
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
const lines = (await guideAt(a.branch as string, ctx, guidePath)).split('\n');
|
|
2240
|
+
for (let i = 0; i < lines.length && out.length < max; i++) {
|
|
2241
|
+
re.lastIndex = 0;
|
|
2242
|
+
if (re.test(lines[i]!)) out.push({ path: guidePath, line: i + 1, text: lines[i]!.slice(0, 300) });
|
|
2243
|
+
}
|
|
2244
|
+
} else if (kind === 'directory') {
|
|
2035
2245
|
await grepWalk(
|
|
2036
2246
|
fs,
|
|
2037
2247
|
searchRoot,
|
|
@@ -330,6 +330,21 @@ export class WorkflowValidationError extends WorkflowDomainError {
|
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
/**
|
|
334
|
+
* The commit an applied change request records is in the clone but is NOT
|
|
335
|
+
* that request's own merge commit — no second parent (not a merge), no first
|
|
336
|
+
* parent (a root commit), or a message that does not name the request. A
|
|
337
|
+
* validation failure like the one above, told apart
|
|
338
|
+
* because it never mends: a reader may remember it, where a commit the clone
|
|
339
|
+
* merely does not hold yet must be asked for again after the next fetch.
|
|
340
|
+
*/
|
|
341
|
+
export class AppliedChangeMismatchError extends WorkflowValidationError {
|
|
342
|
+
constructor(message: string, payload?: Record<string, unknown>) {
|
|
343
|
+
super(message, payload);
|
|
344
|
+
this.name = 'AppliedChangeMismatchError';
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
333
348
|
/**
|
|
334
349
|
* The next step a missing path always offers, in one sentence.
|
|
335
350
|
*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A connected tool Hexis keeps off every agent surface because its input
|
|
3
|
+
* schema is not valid JSON Schema as its server sent it — and the read port
|
|
4
|
+
* the surfaces that SHOW that to the server's owner use.
|
|
5
|
+
*
|
|
6
|
+
* Here rather than in either module because two of them meet over it: the MCP
|
|
7
|
+
* proxy finds these when a server's tools are loaded (`modules/mcp`), and the
|
|
8
|
+
* tool catalog reports them to the people who manage the server
|
|
9
|
+
* (`modules/tool-manuals`, on the tool page and in `list_tool_setup`). Neither
|
|
10
|
+
* has any business importing the other.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** One connected tool hidden from agents, and why. */
|
|
14
|
+
export interface HiddenTool {
|
|
15
|
+
/** The manual (the `.tool` or `mcp.json` server) the tool came from, by catalog name. */
|
|
16
|
+
manual: string;
|
|
17
|
+
/** The name an agent would have called it by, had it been offered. */
|
|
18
|
+
name: string;
|
|
19
|
+
/** JSON Pointer to the place in the input schema that is not valid. */
|
|
20
|
+
path: string;
|
|
21
|
+
/** The violation in the validator's own words, e.g. `must be a string`. */
|
|
22
|
+
reason: string;
|
|
23
|
+
/** The one sentence every owner-facing surface shows, built once so they cannot drift. */
|
|
24
|
+
marker: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What the owner-facing surfaces read. A deployment that never builds an MCP
|
|
29
|
+
* surface has nothing to report, so every consumer treats the port as
|
|
30
|
+
* optional and an absent one as "no tool is hidden".
|
|
31
|
+
*/
|
|
32
|
+
export interface HiddenToolSource {
|
|
33
|
+
/**
|
|
34
|
+
* The tools of `manual` currently hidden for an invalid schema, by the
|
|
35
|
+
* manual's CATALOG name — empty for a healthy server, and empty for a server
|
|
36
|
+
* whose tools this process has not loaded yet.
|
|
37
|
+
*
|
|
38
|
+
* A finding belongs to the SERVER, not to whoever's request loaded it: a
|
|
39
|
+
* schema is a property of the tool definition, and the people who can get it
|
|
40
|
+
* fixed are not necessarily the person whose connection saw it. So this is
|
|
41
|
+
* every finding this process holds for that manual, from whichever callers
|
|
42
|
+
* have loaded it, each distinct defect once.
|
|
43
|
+
*/
|
|
44
|
+
hiddenFor(manual: string): HiddenTool[];
|
|
45
|
+
}
|