@bevel-software/platform-core-backend 0.23.0 → 0.25.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/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +17 -3
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +3 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +25 -1
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +12 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +10 -0
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +16 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +17 -0
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +4 -1
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/directory-sync-bot.d.ts.map +1 -1
- package/dist/modules/access/directory-sync-bot.js +7 -3
- package/dist/modules/access/directory-sync-bot.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +8 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +8 -2
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +38 -6
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +39 -6
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/index.d.ts +2 -1
- package/dist/modules/agent-instructions/index.d.ts.map +1 -1
- package/dist/modules/agent-instructions/index.js +2 -1
- package/dist/modules/agent-instructions/index.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +115 -0
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -0
- package/dist/modules/agent-instructions/shared-file-rules.js +272 -0
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +4 -2
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +57 -16
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +25 -15
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts +20 -2
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +66 -35
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/database/connection.d.ts +16 -0
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +117 -0
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +296 -96
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +81 -31
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/database/migrate.d.ts +95 -1
- package/dist/modules/database/migrate.d.ts.map +1 -1
- package/dist/modules/database/migrate.js +390 -2
- package/dist/modules/database/migrate.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +17 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +29 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +8 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +38 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/plugins/join-request-records.store.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-records.store.js +7 -4
- package/dist/modules/plugins/join-request-records.store.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +8 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +30 -0
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +42 -31
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/description-length.d.ts +80 -0
- package/dist/modules/tool-registry/description-length.d.ts.map +1 -0
- package/dist/modules/tool-registry/description-length.js +108 -0
- package/dist/modules/tool-registry/description-length.js.map +1 -0
- package/dist/modules/workflow/git/git.service.d.ts +25 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +40 -1
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +5 -1
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +1 -1
- package/dist/modules/workflow/recovery-bot.js +7 -3
- package/dist/modules/workflow/recovery-bot.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +4 -1
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-upload.routes.d.ts +77 -0
- package/dist/modules/workspace/agent-upload.routes.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.routes.js +210 -0
- package/dist/modules/workspace/agent-upload.routes.js.map +1 -0
- package/dist/modules/workspace/agent-upload.store.d.ts +284 -0
- package/dist/modules/workspace/agent-upload.store.d.ts.map +1 -0
- package/dist/modules/workspace/agent-upload.store.js +553 -0
- package/dist/modules/workspace/agent-upload.store.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +3 -3
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +40 -0
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +46 -4
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/upload-limits.d.ts +13 -0
- package/dist/modules/workspace/upload-limits.d.ts.map +1 -0
- package/dist/modules/workspace/upload-limits.js +13 -0
- package/dist/modules/workspace/upload-limits.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +1 -1
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +13 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +61 -33
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +11 -9
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +570 -134
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/workspace/write-denial.d.ts +0 -6
- package/dist/modules/workspace/write-denial.d.ts.map +1 -1
- package/dist/modules/workspace/write-denial.js +0 -6
- package/dist/modules/workspace/write-denial.js.map +1 -1
- package/dist/modules/workspace/zip-entry-rules.d.ts +114 -0
- package/dist/modules/workspace/zip-entry-rules.d.ts.map +1 -0
- package/dist/modules/workspace/zip-entry-rules.js +154 -0
- package/dist/modules/workspace/zip-entry-rules.js.map +1 -0
- package/dist/shared/column-crypto.d.ts +194 -0
- package/dist/shared/column-crypto.d.ts.map +1 -0
- package/dist/shared/column-crypto.js +144 -0
- package/dist/shared/column-crypto.js.map +1 -0
- package/dist/shared/token-crypto.d.ts.map +1 -1
- package/dist/shared/token-crypto.js +25 -1
- package/dist/shared/token-crypto.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +1 -0
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/dist/tenancy/tenant-secrets.d.ts +5 -1
- package/dist/tenancy/tenant-secrets.d.ts.map +1 -1
- package/dist/tenancy/tenant-secrets.js +4 -0
- package/dist/tenancy/tenant-secrets.js.map +1 -1
- package/kb-template/AGENTS.md +42 -0
- package/migrations/0016_pii_encryption.sql +20 -0
- package/migrations/meta/0016_snapshot.json +2327 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +3 -3
- package/src/core/__tests__/lifecycle.test.ts +71 -4
- package/src/core/create-core-server.ts +21 -3
- package/src/core/create-core-services.ts +27 -1
- package/src/core/lifecycle.ts +19 -0
- package/src/core-config.ts +18 -0
- package/src/index.ts +25 -0
- package/src/modules/access/__tests__/users-db-double.ts +21 -12
- package/src/modules/access/access.routes.ts +4 -1
- package/src/modules/access/directory-sync-bot.ts +7 -3
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +8 -4
- package/src/modules/agent-instructions/__tests__/compose.test.ts +19 -10
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +238 -0
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -2
- package/src/modules/agent-instructions/compose.ts +50 -7
- package/src/modules/agent-instructions/index.ts +12 -0
- package/src/modules/agent-instructions/shared-file-rules.ts +314 -0
- package/src/modules/audit/agent-audit.service.ts +4 -2
- package/src/modules/auth/__tests__/account-deactivation.test.ts +2 -1
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +6 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +6 -3
- package/src/modules/auth/__tests__/auth.service.test.ts +3 -1
- package/src/modules/auth/account-erasure.service.ts +69 -18
- package/src/modules/auth/auth.service.ts +33 -23
- package/src/modules/code-mode/__tests__/chain-runtime.e2e.test.ts +335 -0
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +46 -2
- package/src/modules/code-mode/code-mode.tool.ts +80 -34
- package/src/modules/database/__tests__/connection.test.ts +12 -0
- package/src/modules/database/__tests__/pii-backfill.pg.test.ts +780 -0
- package/src/modules/database/connection.ts +117 -0
- package/src/modules/database/core-schema.ts +81 -31
- package/src/modules/database/migrate.ts +540 -2
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +93 -0
- package/src/modules/kb-fs/locking-filesystem.ts +37 -0
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +4 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +47 -7
- package/src/modules/mcp/mcp.service.ts +46 -7
- package/src/modules/plugins/join-request-records.store.ts +7 -4
- package/src/modules/tool-auth/external-api-key.service.ts +8 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +151 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +37 -0
- package/src/modules/tool-manuals/tool-manuals.tools.ts +44 -31
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +378 -0
- package/src/modules/tool-registry/description-length.ts +111 -0
- package/src/modules/workflow/git/__tests__/git.service.prFetchFailure.test.ts +170 -0
- package/src/modules/workflow/git/git.service.ts +47 -1
- package/src/modules/workflow/pending-commits.service.ts +5 -1
- package/src/modules/workflow/recovery-bot.ts +7 -3
- package/src/modules/workflow/review-workflow/__tests__/carry-approvals-forward.test.ts +4 -0
- package/src/modules/workflow/review-workflow/__tests__/erase-approver.test.ts +25 -3
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow.service.ts +4 -1
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +1604 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +24 -14
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +156 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +77 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +57 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +39 -36
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +196 -46
- package/src/modules/workspace/agent-upload.routes.ts +214 -0
- package/src/modules/workspace/agent-upload.store.ts +668 -0
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +9 -9
- package/src/modules/workspace/startup/steps/seed-tree.ts +3 -6
- package/src/modules/workspace/startup/steps/template-source.ts +53 -5
- package/src/modules/workspace/upload-limits.ts +12 -0
- package/src/modules/workspace/workspace.routes.ts +1 -2
- package/src/modules/workspace/workspace.service.ts +63 -37
- package/src/modules/workspace/workspace.tools.ts +647 -148
- package/src/modules/workspace/write-denial.ts +0 -8
- package/src/modules/workspace/zip-entry-rules.ts +173 -0
- package/src/shared/__tests__/column-crypto.test.ts +217 -0
- package/src/shared/column-crypto.ts +218 -0
- package/src/shared/token-crypto.ts +28 -1
- package/src/tenancy/__tests__/static-tenant-source.test.ts +4 -0
- package/src/tenancy/static-tenant-source.ts +1 -0
- package/src/tenancy/tenant-secrets.ts +5 -1
|
@@ -18,12 +18,9 @@ import { mergeGroupsIntoRoles, parseRolesYaml } from '../../../../access-model/a
|
|
|
18
18
|
import { TemplateFilesStep } from '../template-files.step.js';
|
|
19
19
|
import { buildSeedTree } from '../seed-tree.js';
|
|
20
20
|
import { defaultKbTemplateDir } from '../../../../../assets.js';
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
renderKbLayoutPlaceholders,
|
|
25
|
-
} from '@bevel-software/platform-shared';
|
|
26
|
-
import { PLATFORM_HEADER, TOOL_PREFIX_LINE, composeAgentInstructions } from '../../../../agent-instructions/index.js';
|
|
21
|
+
import { DEFAULT_KB_LAYOUT, agentsFilePointerSentence } from '@bevel-software/platform-shared';
|
|
22
|
+
import { renderTemplateText } from '../template-source.js';
|
|
23
|
+
import { TOOL_PREFIX_LINE, composeAgentInstructions, platformInstructions } from '../../../../agent-instructions/index.js';
|
|
27
24
|
import { testKbContext } from '../../../../../__tests__/kb-context.js';
|
|
28
25
|
|
|
29
26
|
const execFileAsync = promisify(execFile);
|
|
@@ -148,7 +145,10 @@ const PREAMBLE_IGNORE_BLOCK =
|
|
|
148
145
|
|
|
149
146
|
/** The template as the step writes it under the default layout — placeholders rendered. */
|
|
150
147
|
async function template(name: string): Promise<string> {
|
|
151
|
-
|
|
148
|
+
// `renderTemplateText`, not the layout renderer alone: the managed guide also
|
|
149
|
+
// asks for the shared file rules, and a fixture rendered with half the
|
|
150
|
+
// placeholders filled would read as drifted on every boot.
|
|
151
|
+
return renderTemplateText(await fs.readFile(path.join(TEMPLATE_DIR, name), 'utf8'), DEFAULT_KB_LAYOUT);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/** Every required file + reserved root already present, from the real template. */
|
|
@@ -695,7 +695,7 @@ describe('TemplateFilesStep', () => {
|
|
|
695
695
|
expect((await git(dir, ['rev-list', '--count', 'HEAD'])).trim()).toBe('1');
|
|
696
696
|
});
|
|
697
697
|
|
|
698
|
-
it('ships a template that composes to the platform
|
|
698
|
+
it('ships a template that composes to the platform text alone: one comment, nothing broadcast', async () => {
|
|
699
699
|
const shipped = await template('mcp-description.md');
|
|
700
700
|
expect(shipped.trimStart().startsWith('<!--')).toBe(true);
|
|
701
701
|
expect(shipped.trimEnd().endsWith('-->')).toBe(true);
|
|
@@ -705,7 +705,7 @@ describe('TemplateFilesStep', () => {
|
|
|
705
705
|
expect(shipped).toContain('first paragraph under about 220');
|
|
706
706
|
expect(shipped).not.toContain('{{');
|
|
707
707
|
const composed = composeAgentInstructions(shipped);
|
|
708
|
-
expect(composed.instructions).toBe(
|
|
708
|
+
expect(composed.instructions).toBe(platformInstructions(DEFAULT_KB_LAYOUT));
|
|
709
709
|
expect(composed.toolPrefix).toBe(TOOL_PREFIX_LINE);
|
|
710
710
|
expect(composed.unterminatedComment).toBe(false);
|
|
711
711
|
expect(composed.preambleChars).toBe(0);
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
LEGACY_AGENTS_FILE,
|
|
5
|
-
renderKbLayoutPlaceholders,
|
|
6
|
-
} from '@bevel-software/platform-shared';
|
|
3
|
+
import { LEGACY_AGENTS_FILE } from '@bevel-software/platform-shared';
|
|
7
4
|
import type { IFsProbe, ITreeWalker } from '../../../../shared/fs.contract.js';
|
|
8
5
|
import type { KbContext } from '../../../../shared/kb-context.js';
|
|
9
6
|
import { renderRolesYaml } from '../../../access-model/render-roles-yaml.js';
|
|
10
7
|
import { reservedRootDirs } from './template-files.step.js';
|
|
11
|
-
import { TEMPLATE_SOURCE_FALLBACKS, TemplateSource } from './template-source.js';
|
|
8
|
+
import { TEMPLATE_SOURCE_FALLBACKS, TemplateSource, renderTemplateText } from './template-source.js';
|
|
12
9
|
import { assertNotGitInternals, hasGitInternalsSegment } from '../../../../shared/git-internals.js';
|
|
13
10
|
import { GitInternalsError } from '../../../../shared/domain-errors.js';
|
|
14
11
|
|
|
@@ -192,7 +189,7 @@ class KbSeedTree {
|
|
|
192
189
|
if (text === null) {
|
|
193
190
|
await fs.copyFile(from, to);
|
|
194
191
|
} else {
|
|
195
|
-
await fs.writeFile(to,
|
|
192
|
+
await fs.writeFile(to, renderTemplateText(text, this.kb.layout), 'utf8');
|
|
196
193
|
}
|
|
197
194
|
await fs.chmod(to, (await fs.stat(from)).mode & 0o777);
|
|
198
195
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { gitignoreLiteral, renderKbLayoutPlaceholders } from '@bevel-software/platform-shared';
|
|
2
|
+
import { gitignoreLiteral, renderKbLayoutPlaceholders, type KbLayout } from '@bevel-software/platform-shared';
|
|
3
3
|
import { IGNORE_FILENAME, isAbsence, type EntryStat, type IFsProbe } from '../../../../shared/fs.contract.js';
|
|
4
4
|
import type { KbContext } from '../../../../shared/kb-context.js';
|
|
5
|
+
|
|
5
6
|
import { PREAMBLE_FILE } from '../../../agent-instructions/compose.js';
|
|
7
|
+
import { sharedFileRulesSection } from '../../../agent-instructions/shared-file-rules.js';
|
|
6
8
|
import { defaultKbTemplateDir } from '../../../../assets.js';
|
|
7
9
|
import { logger } from '../../../../shared/logging.js';
|
|
8
10
|
|
|
@@ -37,6 +39,42 @@ export const TEMPLATE_SOURCE_FALLBACKS: ReadonlyMap<string, string> = new Map([
|
|
|
37
39
|
['.gitignore', 'gitignore.template'],
|
|
38
40
|
]);
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Where the managed guide asks for the rules every file tool shares. Not a
|
|
44
|
+
* layout placeholder: the layout renderer lives in `platform-shared`, which
|
|
45
|
+
* the frontend also reads, and the rules are backend text.
|
|
46
|
+
*/
|
|
47
|
+
export const SHARED_FILE_RULES_PLACEHOLDER = '{{sharedFileRules}}';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Every placeholder a template file carries, filled: the layout's names on the
|
|
51
|
+
* text an author wrote, THEN the shared file rules dropped into the hole they
|
|
52
|
+
* left. ONE function, because the two writers of template content — the top-up
|
|
53
|
+
* step through {@link TemplateSource.read} and the empty-remote seeder, which
|
|
54
|
+
* streams files itself — must render identically or a freshly seeded knowledge
|
|
55
|
+
* base would carry a guide with a literal `{{sharedFileRules}}` in it.
|
|
56
|
+
*
|
|
57
|
+
* THAT ORDER, not the other one: the rules are already rendered for this
|
|
58
|
+
* layout (`sharedFileRulesSection` takes it), so running the layout renderer
|
|
59
|
+
* over them again would only reach text they STATE rather than contain — a
|
|
60
|
+
* folder a deployment really named `{{skillsDir}}` would come back as some
|
|
61
|
+
* other folder's name, and the rule would then point at a folder that is not
|
|
62
|
+
* the one it refuses a move out of. Injecting last leaves the section byte for
|
|
63
|
+
* byte what both channels carry, and costs nothing:
|
|
64
|
+
* `renderKbLayoutPlaceholders` touches only its own four tokens, so the
|
|
65
|
+
* placeholder for the rules survives the first pass untouched.
|
|
66
|
+
*
|
|
67
|
+
* A replacer FUNCTION for the rules: they carry dollar sequences (a `${VAR}`
|
|
68
|
+
* a template writes) that a string replacement would read as capture-group
|
|
69
|
+
* syntax.
|
|
70
|
+
*/
|
|
71
|
+
export function renderTemplateText(text: string, layout: KbLayout): string {
|
|
72
|
+
return renderKbLayoutPlaceholders(text, layout).replaceAll(
|
|
73
|
+
SHARED_FILE_RULES_PLACEHOLDER,
|
|
74
|
+
() => sharedFileRulesSection(layout),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
40
78
|
/**
|
|
41
79
|
* ONE template directory, read through ONE disk port — what both readers of
|
|
42
80
|
* the KB template share.
|
|
@@ -112,6 +150,16 @@ export class TemplateSource {
|
|
|
112
150
|
* placeholders the template carries (`{{pluginsDir}}` …) are filled with
|
|
113
151
|
* the names in effect. Every required file is text; a template without
|
|
114
152
|
* placeholders passes through unchanged.
|
|
153
|
+
*
|
|
154
|
+
* `{{sharedFileRules}}` is filled from the ONE source text the MCP handshake
|
|
155
|
+
* also sends (see `shared-file-rules.ts`), so the rules every file tool
|
|
156
|
+
* shares are stated in the guide and in the instructions without being
|
|
157
|
+
* written twice. Filled AFTER the layout pass (see
|
|
158
|
+
* {@link renderTemplateText}): the rules come back already rendered for this
|
|
159
|
+
* layout, so a layout pass over them could only misread a name they STATE as
|
|
160
|
+
* a placeholder. The two passes do not collide — the layout renderer touches
|
|
161
|
+
* only its own four tokens, so `{{sharedFileRules}}` reaches the second pass
|
|
162
|
+
* untouched.
|
|
115
163
|
*/
|
|
116
164
|
async read(relPath: string): Promise<string> {
|
|
117
165
|
let raw: string;
|
|
@@ -134,13 +182,13 @@ export class TemplateSource {
|
|
|
134
182
|
// reads as syntax (`#Guide.md`, `!Guide.md`, brackets) would hide nothing
|
|
135
183
|
// written bare. Escaped there, and only there: everywhere else the
|
|
136
184
|
// placeholder is prose. ONE render, with the escaped name as the layout's
|
|
137
|
-
// —
|
|
138
|
-
// to contain a placeholder as one.
|
|
185
|
+
// — rendering twice over text already rendered would read a name that
|
|
186
|
+
// happens to contain a placeholder as one.
|
|
139
187
|
const layout = this.kb.layout;
|
|
140
188
|
if (relPath === IGNORE_FILENAME) {
|
|
141
|
-
return
|
|
189
|
+
return renderTemplateText(raw, { ...layout, agentsFile: gitignoreLiteral(layout.agentsFile) });
|
|
142
190
|
}
|
|
143
|
-
return
|
|
191
|
+
return renderTemplateText(raw, layout);
|
|
144
192
|
}
|
|
145
193
|
|
|
146
194
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deployment's own upload ceiling, in bytes.
|
|
3
|
+
*
|
|
4
|
+
* ONE number for every surface that accepts bytes: the app's
|
|
5
|
+
* `POST /workspace/:id/upload` (a person dropping a file in the explorer) and
|
|
6
|
+
* the agent upload route (`POST /agent/uploads/:token`). They used to be the
|
|
7
|
+
* same number written twice, which is the shape a limit drifts in — an agent
|
|
8
|
+
* refused at 50 MB by one route and accepted by the other would have no way to
|
|
9
|
+
* read which limit applied. The refusal NAMES this value, so a caller can size
|
|
10
|
+
* its next attempt from the answer rather than by bisection.
|
|
11
|
+
*/
|
|
12
|
+
export const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
@@ -38,8 +38,7 @@ import { createGitInternalsRouteGuard } from './git-internals.middleware.js';
|
|
|
38
38
|
import { removeEmptyDirs } from './empty-dirs.js';
|
|
39
39
|
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
40
40
|
import type { SkillSaveCheck } from './workspace.tools.js';
|
|
41
|
-
|
|
42
|
-
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
41
|
+
import { MAX_UPLOAD_BYTES } from './upload-limits.js';
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
44
|
* One file identity from one request field, or `null` when the caller sent
|
|
@@ -8,7 +8,6 @@ import type { AuthUser, IWorkspaceService, WorkspaceInfo, FileTreeEntry } from '
|
|
|
8
8
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
9
|
import {
|
|
10
10
|
validateRelativePath,
|
|
11
|
-
validateFilename,
|
|
12
11
|
FOLDER_PLACEHOLDER,
|
|
13
12
|
isFolderPlaceholder,
|
|
14
13
|
entryExistsMessage,
|
|
@@ -26,6 +25,7 @@ import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
|
|
|
26
25
|
import { assertWithinDirectory } from '../../shared/path-containment.js';
|
|
27
26
|
import { assertRepoRootNameFree, normalizeWorkspacePath } from '../kb-fs/repo-path.js';
|
|
28
27
|
import { assertNoGitInternalsSegment, assertNotGitInternals, hasGitInternalsSegment } from '../../shared/git-internals.js';
|
|
28
|
+
import { isZipNoiseEntry, readZipEntry, zipEntryName, zipEntryNameRefusal, zipEntrySegments } from './zip-entry-rules.js';
|
|
29
29
|
import {
|
|
30
30
|
GitInternalsError,
|
|
31
31
|
PathNotFoundError,
|
|
@@ -495,6 +495,19 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
495
495
|
* a conventional default branch isn't on the remote) just to run a global op.
|
|
496
496
|
* Scans disk rather than the in-memory `branchDirs` map so clones that
|
|
497
497
|
* survived a process restart are still found.
|
|
498
|
+
*
|
|
499
|
+
* INTERCHANGEABLE ONLY ONCE IT CAN REACH THE REMOTE. The clone handed back
|
|
500
|
+
* is one this process may never have opened, and opening a branch is the
|
|
501
|
+
* only other place a clone's credential helper is brought into line with the
|
|
502
|
+
* deployment's (`getOrCreateForBranch`). A clone made before the helper was
|
|
503
|
+
* persisted, or before a token was configured, has none — and it is exactly
|
|
504
|
+
* the kind that sorts first on disk, because it is old. Every repo-global
|
|
505
|
+
* operation then ran its fetches in a working copy that could not
|
|
506
|
+
* authenticate: the fetch failed, quietly, and each change request whose
|
|
507
|
+
* branch that clone had never seen was reported as `unknown branch`, list
|
|
508
|
+
* after list, while the branches sat on the remote. So the helper is stamped
|
|
509
|
+
* here too, before the clone is offered. A no-op for a clone already stamped
|
|
510
|
+
* in this process, and it never throws (see `stampCredentialHelper`).
|
|
498
511
|
*/
|
|
499
512
|
async findAnyWorkspaceId(): Promise<string | null> {
|
|
500
513
|
let entries: Array<{ name: string; isDirectory: () => boolean }>;
|
|
@@ -505,12 +518,15 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
505
518
|
}
|
|
506
519
|
for (const entry of entries) {
|
|
507
520
|
if (!entry.isDirectory()) continue;
|
|
521
|
+
const repoDir = path.join(this.workspacesRoot, entry.name, this.kbDirName);
|
|
508
522
|
try {
|
|
509
|
-
await fs.access(path.join(
|
|
510
|
-
return entry.name;
|
|
523
|
+
await fs.access(path.join(repoDir, '.git'));
|
|
511
524
|
} catch {
|
|
512
525
|
// No `.git` — half-built or unrelated dir; keep looking.
|
|
526
|
+
continue;
|
|
513
527
|
}
|
|
528
|
+
await this.refreshCredentialHelperIfStale(branchForWorkspaceId(entry.name), repoDir);
|
|
529
|
+
return entry.name;
|
|
514
530
|
}
|
|
515
531
|
return null;
|
|
516
532
|
}
|
|
@@ -688,6 +704,15 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
688
704
|
const startedMeanwhile = this.inFlightSetAsides.get(branch);
|
|
689
705
|
if (startedMeanwhile) {
|
|
690
706
|
await startedMeanwhile;
|
|
707
|
+
} else if (this.discardsOf(id) !== discardsAtAdoption) {
|
|
708
|
+
// The copy whose address this call read is no longer there: a move
|
|
709
|
+
// began AND ended while it was being read. An in-flight entry is
|
|
710
|
+
// cleared the moment its move ends, so a caller that comes back after
|
|
711
|
+
// that finds nobody to wait for — and its answer is about a copy that
|
|
712
|
+
// has already been set aside. Acting on it would set aside whatever is
|
|
713
|
+
// at this path NOW, which is the fresh clone the first caller is in
|
|
714
|
+
// the middle of making. Nothing is left to move; the bootstrap below
|
|
715
|
+
// waits for that clone.
|
|
691
716
|
} else {
|
|
692
717
|
const kept = path.join(setAsideRootFor(this.workspacesRoot, this.setAsideRoot), setAsideStamp(), id);
|
|
693
718
|
// What surrounds the move may wait, and the startup phase may set
|
|
@@ -698,6 +723,11 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
698
723
|
if (this.discardsOf(id) !== discardsAtAdoption) return;
|
|
699
724
|
await setAsideClone(repoDir, kept);
|
|
700
725
|
moved = true;
|
|
726
|
+
// Said the way the startup phase says it: the copy at this path was
|
|
727
|
+
// taken away. That is what the check above reads, so every caller
|
|
728
|
+
// that looked at the old copy before this line knows its answer is
|
|
729
|
+
// stale, however late it comes back.
|
|
730
|
+
this.forgetClone(id);
|
|
701
731
|
}).finally(() => {
|
|
702
732
|
this.inFlightSetAsides.delete(branch);
|
|
703
733
|
});
|
|
@@ -1997,43 +2027,22 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
1997
2027
|
continue;
|
|
1998
2028
|
}
|
|
1999
2029
|
processedCount++;
|
|
2000
|
-
const rawName = entry.entryName
|
|
2001
|
-
|
|
2002
|
-
if (
|
|
2003
|
-
rawName.startsWith('__MACOSX/') ||
|
|
2004
|
-
rawName === '__MACOSX' ||
|
|
2005
|
-
rawName.endsWith('/.DS_Store') ||
|
|
2006
|
-
rawName === '.DS_Store' ||
|
|
2007
|
-
/(^|\/)\._/.test(rawName)
|
|
2008
|
-
) {
|
|
2009
|
-
continue;
|
|
2010
|
-
}
|
|
2030
|
+
const rawName = zipEntryName(entry.entryName);
|
|
2011
2031
|
|
|
2012
|
-
if (
|
|
2013
|
-
skipped.push({ path: rawName || '(empty)', reason: 'Invalid path' });
|
|
2014
|
-
continue;
|
|
2015
|
-
}
|
|
2032
|
+
if (isZipNoiseEntry(rawName)) continue;
|
|
2016
2033
|
|
|
2017
|
-
//
|
|
2018
|
-
|
|
2019
|
-
|
|
2034
|
+
// What an entry's NAME may be — invalid path, the git folder in any
|
|
2035
|
+
// spelling, a segment no filesystem keeps intact — is `zip-entry-rules`,
|
|
2036
|
+
// so `apply_file_upload` judges the same names by the same rules rather
|
|
2037
|
+
// than by a second copy of them.
|
|
2038
|
+
const nameRefusal = zipEntryNameRefusal(rawName);
|
|
2039
|
+
if (nameRefusal !== null) {
|
|
2040
|
+
skipped.push({ path: rawName || '(empty)', reason: nameRefusal });
|
|
2020
2041
|
continue;
|
|
2021
2042
|
}
|
|
2022
2043
|
|
|
2023
2044
|
const trimmed = rawName.replace(/\/+$/, '');
|
|
2024
|
-
const segments =
|
|
2025
|
-
let invalidReason: string | null = null;
|
|
2026
|
-
for (const segment of segments) {
|
|
2027
|
-
const reason = validateFilename(segment);
|
|
2028
|
-
if (reason) {
|
|
2029
|
-
invalidReason = reason;
|
|
2030
|
-
break;
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
if (invalidReason) {
|
|
2034
|
-
skipped.push({ path: rawName, reason: invalidReason });
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2045
|
+
const segments = zipEntrySegments(rawName);
|
|
2037
2046
|
|
|
2038
2047
|
const targetAbsolute = path.resolve(destAbsolute, ...segments);
|
|
2039
2048
|
const destRoot = path.resolve(destAbsolute);
|
|
@@ -2114,14 +2123,31 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
2114
2123
|
continue;
|
|
2115
2124
|
}
|
|
2116
2125
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2126
|
+
// Read through the one bounded reader (`readZipEntry`), never with the
|
|
2127
|
+
// declared size above as the only bound: an entry that declares ZERO is
|
|
2128
|
+
// inflated with no cap at all by the archive reader, so a few kilobytes
|
|
2129
|
+
// could expand to whatever memory there is before the checks below ran.
|
|
2130
|
+
// Capped at what is left of both limits, and a read that fails is this
|
|
2131
|
+
// entry's refusal rather than the whole archive's.
|
|
2132
|
+
const read = readZipEntry(
|
|
2133
|
+
entry,
|
|
2134
|
+
Math.min(UNZIP_MAX_ENTRY_BYTES, UNZIP_MAX_TOTAL_BYTES - totalUncompressed),
|
|
2135
|
+
);
|
|
2136
|
+
if (!read.ok && read.reason === 'unreadable') {
|
|
2137
|
+
skipped.push({ path: rawName, reason: `Entry could not be read: ${read.detail}` });
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
if (!read.ok) {
|
|
2119
2141
|
skipped.push({
|
|
2120
2142
|
path: rawName,
|
|
2121
|
-
reason:
|
|
2143
|
+
reason:
|
|
2144
|
+
totalUncompressed + UNZIP_MAX_ENTRY_BYTES > UNZIP_MAX_TOTAL_BYTES
|
|
2145
|
+
? `Archive exceeds ${UNZIP_MAX_TOTAL_BYTES} byte total uncompressed limit`
|
|
2146
|
+
: `Entry exceeds ${UNZIP_MAX_ENTRY_BYTES} byte per-file limit`,
|
|
2122
2147
|
});
|
|
2123
2148
|
continue;
|
|
2124
2149
|
}
|
|
2150
|
+
const data = read.data;
|
|
2125
2151
|
if (totalUncompressed + data.byteLength > UNZIP_MAX_TOTAL_BYTES) {
|
|
2126
2152
|
skipped.push({
|
|
2127
2153
|
path: rawName,
|