@bevel-software/platform-core-backend 0.12.0 → 0.13.1
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/THIRD-PARTY-NOTICES.md +9 -7
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +8 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +58 -2
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +174 -33
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +1 -0
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +1 -1
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access-model/access-errors.d.ts +11 -0
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +14 -0
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +24 -8
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +64 -3
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +42 -0
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -0
- package/dist/modules/declared-variables/declared-variables.routes.js +135 -0
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -0
- package/dist/modules/declared-variables/index.d.ts +2 -0
- package/dist/modules/declared-variables/index.d.ts.map +1 -0
- package/dist/modules/declared-variables/index.js +2 -0
- package/dist/modules/declared-variables/index.js.map +1 -0
- package/dist/modules/diff/diff.routes.d.ts +1 -1
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +3 -3
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/kb-fs/clone-config.d.ts +40 -2
- package/dist/modules/kb-fs/clone-config.d.ts.map +1 -1
- package/dist/modules/kb-fs/clone-config.js +94 -2
- package/dist/modules/kb-fs/clone-config.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +16 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +20 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +32 -0
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -0
- package/dist/modules/kb-fs/repo-path.js +54 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -0
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +60 -18
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts +20 -0
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.js +121 -47
- package/dist/modules/secrets-vault/mcp-oauth-discovery.service.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 +20 -2
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +1 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +45 -12
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +2 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +40 -8
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +38 -14
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +164 -55
- 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 +10 -5
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-manuals/utcp-cli-parse-only.d.ts +55 -0
- package/dist/modules/tool-manuals/utcp-cli-parse-only.d.ts.map +1 -0
- package/dist/modules/tool-manuals/utcp-cli-parse-only.js +76 -0
- package/dist/modules/tool-manuals/utcp-cli-parse-only.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +3 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +19 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +32 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +70 -5
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +3 -3
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +20 -2
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +8 -0
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +74 -16
- package/dist/modules/workflow/pending-commits.worker.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 +7 -0
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +12 -0
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +39 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +116 -6
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +21 -4
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +52 -8
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +121 -23
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +31 -15
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/kb-template/AGENTS.md +52 -8
- package/package.json +6 -5
- package/src/core/create-core-server.ts +13 -1
- package/src/core/create-core-services.ts +1 -0
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +260 -0
- package/src/modules/access/__tests__/access-groups.test.ts +28 -0
- package/src/modules/access/access-control.service.ts +198 -37
- package/src/modules/access/admin-locked-commit.ts +1 -0
- package/src/modules/access/synced-groups-committer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +101 -1
- package/src/modules/access-model/access-errors.ts +19 -0
- package/src/modules/access-model/access-grammar.ts +67 -3
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +166 -0
- package/src/modules/declared-variables/declared-variables.routes.ts +151 -0
- package/src/modules/declared-variables/index.ts +1 -0
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +4 -4
- package/src/modules/diff/diff.routes.ts +3 -2
- package/src/modules/kb-fs/__tests__/clone-config.test.ts +63 -2
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +306 -131
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +106 -0
- package/src/modules/kb-fs/clone-config.ts +97 -2
- package/src/modules/kb-fs/locking-filesystem.ts +30 -0
- package/src/modules/kb-fs/repo-path.ts +56 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +104 -0
- package/src/modules/secrets-vault/__tests__/mcp-oauth-discovery.service.test.ts +52 -0
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +48 -1
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +73 -22
- package/src/modules/secrets-vault/mcp-oauth-discovery.service.ts +141 -50
- package/src/modules/secrets-vault/secrets-vault.routes.ts +20 -2
- package/src/modules/tool-helpers/tool-context.ts +1 -0
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +38 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +2 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +243 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +95 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +17 -1
- package/src/modules/tool-manuals/mcp-json-discovery.ts +39 -15
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +2 -1
- package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -9
- package/src/modules/tool-manuals/tool-manuals.service.ts +156 -28
- package/src/modules/tool-manuals/tool-manuals.tools.ts +10 -5
- package/src/modules/tool-manuals/utcp-cli-parse-only.ts +76 -0
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +46 -0
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +11 -5
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +172 -7
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +60 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +18 -1
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +56 -0
- package/src/modules/workflow/git/__tests__/git.service.commitFile.strayPath.test.ts +162 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.getPrDetail.test.ts +114 -0
- package/src/modules/workflow/git/git.service.ts +73 -6
- package/src/modules/workflow/git/pull-request.service.ts +22 -4
- package/src/modules/workflow/pending-commits.worker.ts +80 -18
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +60 -0
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +5 -0
- package/src/modules/workflow/workflow.routes.ts +12 -0
- package/src/modules/workflow/workflow.service.ts +123 -7
- package/src/modules/workspace/__tests__/workspace.service.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +45 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +141 -0
- package/src/modules/workspace/startup/kb-git.ts +20 -7
- package/src/modules/workspace/workspace.service.ts +132 -25
- package/src/modules/workspace/workspace.tools.ts +35 -15
|
@@ -9,7 +9,12 @@ import { BevelIgnoreStack } from './bevel-ignore.js';
|
|
|
9
9
|
import { workspaceIdForBranch, branchForWorkspaceId } from '../../shared/workspace-id.js';
|
|
10
10
|
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
11
11
|
import { assertValidBranchName } from '../kb-fs/branch-name.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
cloneCredentialArgs,
|
|
14
|
+
cloneCredentialConfigArgs,
|
|
15
|
+
cloneTrackingConfigArgs,
|
|
16
|
+
SAFE_IMPLICIT_FETCH_ARGS,
|
|
17
|
+
} from '../kb-fs/clone-config.js';
|
|
13
18
|
import type { IDiffService } from '../diff/diff.interface.js';
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -155,9 +160,13 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
155
160
|
this.onWorkspaceCloned = listener;
|
|
156
161
|
}
|
|
157
162
|
|
|
158
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* Build git clone args. Credentials go in as `clone --config`, which both
|
|
165
|
+
* authenticates this clone and leaves the helper in the new repo's config for
|
|
166
|
+
* every later push — and keeps the token itself out of the repo URL, out of
|
|
167
|
+
* argv, and out of `.git/config` (the helper reads it from the environment).
|
|
168
|
+
*/
|
|
159
169
|
private gitCloneArgs(targetDir: string, branch: string, referenceRepo?: string): string[] {
|
|
160
|
-
const token = process.env.GITHUB_TOKEN;
|
|
161
170
|
// core.longpaths=true: KB has paths >260 chars; without this the clone silently drops those files on Windows.
|
|
162
171
|
const args = ['clone', '-c', 'core.longpaths=true', '-b', branch];
|
|
163
172
|
// Borrow the object database of an existing sibling clone. Every commit
|
|
@@ -171,14 +180,10 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
171
180
|
if (referenceRepo) {
|
|
172
181
|
args.push('--reference', referenceRepo, '--dissociate');
|
|
173
182
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
args.push(
|
|
179
|
-
'-c', `credential.helper=!f() { echo "username=${this.gitUsername()}"; echo "password=$GITHUB_TOKEN"; }; f`,
|
|
180
|
-
);
|
|
181
|
-
}
|
|
183
|
+
// Persisted into the new repo's config (`clone --config`), not just applied
|
|
184
|
+
// to this invocation — every later push from this clone depends on finding
|
|
185
|
+
// the helper there. See `cloneCredentialArgs`.
|
|
186
|
+
args.push(...cloneCredentialArgs(this.gitUsername()));
|
|
182
187
|
args.push(this.kbRepoUrl(), targetDir);
|
|
183
188
|
return args;
|
|
184
189
|
}
|
|
@@ -288,6 +293,11 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
288
293
|
// back a half-cloned workspace.
|
|
289
294
|
if (this.branchDirs.has(branch)) {
|
|
290
295
|
await this.awaitBootstrapOrEvict(branch);
|
|
296
|
+
// A token or username set/changed through the setup screen after this
|
|
297
|
+
// branch was first opened has to reach the already-cached clone, or its
|
|
298
|
+
// pushes keep failing — the fast path is exactly where that rotation is
|
|
299
|
+
// otherwise missed.
|
|
300
|
+
await this.refreshCredentialHelperIfStale(branch, repoDir);
|
|
291
301
|
return this.buildWorkspaceInfo(branch, workspaceDir);
|
|
292
302
|
}
|
|
293
303
|
|
|
@@ -301,6 +311,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
301
311
|
// single-flight wait the cached fast path already uses.
|
|
302
312
|
await this.awaitBootstrapOrEvict(branch);
|
|
303
313
|
if (this.branchDirs.has(branch)) {
|
|
314
|
+
await this.refreshCredentialHelperIfStale(branch, repoDir);
|
|
304
315
|
return this.buildWorkspaceInfo(branch, workspaceDir);
|
|
305
316
|
}
|
|
306
317
|
|
|
@@ -313,7 +324,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
313
324
|
// (duplicate fetch refspec / merge ref) is repaired before anything
|
|
314
325
|
// pulls it. Once per branch per process — the cached paths above return
|
|
315
326
|
// before reaching here.
|
|
316
|
-
await this.
|
|
327
|
+
await this.normalizeCloneConfig(repoDir, branch);
|
|
317
328
|
this.branchDirs.set(branch, workspaceDir);
|
|
318
329
|
return this.buildWorkspaceInfo(branch, workspaceDir);
|
|
319
330
|
} catch {
|
|
@@ -396,17 +407,26 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
396
407
|
}
|
|
397
408
|
|
|
398
409
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
410
|
+
* Bring an adopted clone's config back to what a fresh clone would carry.
|
|
411
|
+
*
|
|
412
|
+
* Tracking: collapse it to a single fetch refspec and a single upstream ref
|
|
413
|
+
* for `branch` (see `kb-fs/clone-config.ts`). A clone that accumulated a
|
|
414
|
+
* second `remote.origin.fetch` refspec or `branch.<b>.merge` value makes git
|
|
415
|
+
* refuse to refresh it — "Cannot rebase onto multiple branches" — which is
|
|
416
|
+
* how a post-merge pull of the target branch fails.
|
|
417
|
+
*
|
|
418
|
+
* Credentials: re-stamp the helper. This is the repair for clones that never
|
|
419
|
+
* got one — anything created by a build whose clone authenticated only its
|
|
420
|
+
* own invocation, or by a deployment that had no token configured until
|
|
421
|
+
* after the clone existed. Without it those clones are permanently
|
|
422
|
+
* unpushable: nothing re-clones a directory that is already on disk, so no
|
|
423
|
+
* restart, re-pull or settings change would ever fix them.
|
|
404
424
|
*
|
|
405
425
|
* Never throws: it only ever touches `.git/config`, so a failure leaves the
|
|
406
426
|
* workspace exactly as usable as it was, and the git layer self-heals the
|
|
407
|
-
*
|
|
427
|
+
* tracking keys before it pulls.
|
|
408
428
|
*/
|
|
409
|
-
private async
|
|
429
|
+
private async normalizeCloneConfig(repoDir: string, branch: string): Promise<void> {
|
|
410
430
|
try {
|
|
411
431
|
for (const args of cloneTrackingConfigArgs(branch)) {
|
|
412
432
|
await execFileAsync('git', ['-C', repoDir, ...args]);
|
|
@@ -415,12 +435,99 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
415
435
|
// One line per branch, not per key: every key writes to the same
|
|
416
436
|
// `.git/config`, so what fails for one fails for all.
|
|
417
437
|
console.warn(
|
|
418
|
-
`[workspace] could not normalize the
|
|
438
|
+
`[workspace] could not normalize the config of the "${branch}" clone:`,
|
|
439
|
+
redactError(err),
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
await this.stampCredentialHelper(repoDir, branch);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Fingerprint of the credential state a clone's helper is derived from: the
|
|
447
|
+
* username (baked into the helper literal) and whether a token exists at all
|
|
448
|
+
* (the helper is present-or-absent on that). The token VALUE is deliberately
|
|
449
|
+
* excluded — the helper reads `$GITHUB_TOKEN` at call time, so rotating the
|
|
450
|
+
* token needs no re-stamp; only a username change or a token appearing /
|
|
451
|
+
* disappearing does.
|
|
452
|
+
*/
|
|
453
|
+
private credentialFingerprint(): string {
|
|
454
|
+
return `${this.gitUsername()}::${process.env.GITHUB_TOKEN ? '1' : '0'}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Last credential fingerprint stamped into each branch's clone, this process. */
|
|
458
|
+
private readonly stampedCredentialFingerprint = new Map<string, string>();
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Bring `repoDir`'s credential helper into line with the current deployment
|
|
462
|
+
* config (stamp when a token is set, unset when none is), and record the
|
|
463
|
+
* fingerprint so the cached fast path can skip a no-op re-stamp. Runs the
|
|
464
|
+
* config args tolerantly and one at a time: `--unset-all` on a clone that
|
|
465
|
+
* never had a helper exits non-zero, and that must neither abort the rest
|
|
466
|
+
* nor read as a failure.
|
|
467
|
+
*/
|
|
468
|
+
private async stampCredentialHelper(repoDir: string, branch: string): Promise<void> {
|
|
469
|
+
let argLists: string[][];
|
|
470
|
+
try {
|
|
471
|
+
argLists = cloneCredentialConfigArgs(this.gitUsername());
|
|
472
|
+
} catch (err) {
|
|
473
|
+
// `credentialHelperValue` fails closed on a username outside its safe
|
|
474
|
+
// charset. Normally unreachable (CoreConfig and the settings service
|
|
475
|
+
// validate first), but if a fourth, unvalidated path ever appears the
|
|
476
|
+
// throw must not escape here: in the disk-adoption block it would land
|
|
477
|
+
// in a catch that reads every error as "not on disk" and kick off a
|
|
478
|
+
// re-clone that fails on the same validation — masking the real cause.
|
|
479
|
+
// Contain it as a loud non-stamp instead; `normalizeCloneConfig`'s
|
|
480
|
+
// never-throws contract stays true.
|
|
481
|
+
console.warn(
|
|
482
|
+
`[workspace] refusing to stamp the credential helper of the "${branch}" clone:`,
|
|
419
483
|
redactError(err),
|
|
420
484
|
);
|
|
485
|
+
this.stampedCredentialFingerprint.delete(branch);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
let stamped = true;
|
|
489
|
+
for (const args of argLists) {
|
|
490
|
+
try {
|
|
491
|
+
await execFileAsync('git', ['-C', repoDir, ...args]);
|
|
492
|
+
} catch (err) {
|
|
493
|
+
// `--unset-all` with no matching value (exit 5) is the expected no-op
|
|
494
|
+
// for a clone that never carried an app helper; anything else is a
|
|
495
|
+
// real failure.
|
|
496
|
+
const code = (err as { code?: number } | null)?.code;
|
|
497
|
+
if (!(args.includes('--unset-all') && code === 5)) {
|
|
498
|
+
stamped = false;
|
|
499
|
+
console.warn(
|
|
500
|
+
`[workspace] could not stamp the credential helper of the "${branch}" clone:`,
|
|
501
|
+
redactError(err),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (stamped) {
|
|
507
|
+
this.stampedCredentialFingerprint.set(branch, this.credentialFingerprint());
|
|
508
|
+
} else {
|
|
509
|
+
// Recording the fingerprint after a FAILED write would make every later
|
|
510
|
+
// cached open skip the retry as "already up to date" — the clone would
|
|
511
|
+
// keep pushing with stale or missing credentials until a restart.
|
|
512
|
+
// Dropping the entry keeps the fast path retrying until a stamp lands.
|
|
513
|
+
this.stampedCredentialFingerprint.delete(branch);
|
|
421
514
|
}
|
|
422
515
|
}
|
|
423
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Re-stamp a cached clone's credential helper when the deployment's
|
|
519
|
+
* credential config changed since we last stamped it — the rotation case the
|
|
520
|
+
* disk-adoption path in `getOrCreateForBranch` never reaches, because the
|
|
521
|
+
* fast path returns before it. Without this, a token added (or a username
|
|
522
|
+
* changed) through the setup screen after a branch was first opened would
|
|
523
|
+
* never reach that branch's clone until a process restart, and its pushes
|
|
524
|
+
* would keep failing exactly as in the original incident.
|
|
525
|
+
*/
|
|
526
|
+
private async refreshCredentialHelperIfStale(branch: string, repoDir: string): Promise<void> {
|
|
527
|
+
if (this.stampedCredentialFingerprint.get(branch) === this.credentialFingerprint()) return;
|
|
528
|
+
await this.stampCredentialHelper(repoDir, branch);
|
|
529
|
+
}
|
|
530
|
+
|
|
424
531
|
private async cloneProcessMapForBranch(workspaceDir: string, branch: string): Promise<void> {
|
|
425
532
|
const targetDir = path.join(workspaceDir, this.kbDirName);
|
|
426
533
|
|
|
@@ -465,10 +572,10 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
465
572
|
}
|
|
466
573
|
if (alreadyCloned) {
|
|
467
574
|
// We don't auto-pull because that could clobber another user's
|
|
468
|
-
// in-progress lock-held edits. The
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
await this.
|
|
575
|
+
// in-progress lock-held edits. The CONFIG is re-stamped though — it
|
|
576
|
+
// never touches the working tree, and a clone with drifted tracking
|
|
577
|
+
// or a missing credential helper is one that can't pull or push.
|
|
578
|
+
await this.normalizeCloneConfig(targetDir, branch);
|
|
472
579
|
return;
|
|
473
580
|
}
|
|
474
581
|
// Half-built dir — wipe and re-clone.
|
|
@@ -492,7 +599,7 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
492
599
|
// this branch. `git clone -b` already produces that shape; stamping it
|
|
493
600
|
// explicitly means the shape is asserted rather than assumed, and the
|
|
494
601
|
// same call is what repairs an existing clone that drifted.
|
|
495
|
-
await this.
|
|
602
|
+
await this.normalizeCloneConfig(targetDir, branch);
|
|
496
603
|
console.log(
|
|
497
604
|
`[workspace] Cloned ${this.kbDirName} for branch "${branch}"` +
|
|
498
605
|
(reference ? ' (referenced a sibling clone)' : ''),
|
|
@@ -20,6 +20,7 @@ import { workspaceIdForBranch } from '../../shared/workspace-id.js';
|
|
|
20
20
|
// Leaf-level shared primitive (same exception `workspace.service.ts` already
|
|
21
21
|
// relies on) — not a workflow service, so this stays inside the module boundary.
|
|
22
22
|
import { assertValidBranchName } from '../kb-fs/branch-name.js';
|
|
23
|
+
import { assertInsideRepo } from '../kb-fs/repo-path.js';
|
|
23
24
|
import type { ISessionSink } from './session-sink.js';
|
|
24
25
|
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
25
26
|
import { toKbRelative, resolveReadableMap } from '../access-model/kb-read-filter.js';
|
|
@@ -111,6 +112,22 @@ const int = (description: string): JsonSchema => ({ type: 'integer', description
|
|
|
111
112
|
|
|
112
113
|
const str = (description: string): JsonSchema => ({ type: 'string', description });
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* A path input that names the clone folder. The tools are rooted at the
|
|
117
|
+
* WORKSPACE dir, one level above the git clone, so a path only reaches git
|
|
118
|
+
* when it starts with that folder; an agent that reads `KnowledgeBase/Foo.md`
|
|
119
|
+
* in a URL or a doc and passes it verbatim would otherwise write beside the
|
|
120
|
+
* repository. Saying so in the input itself, not only in prose, is what the
|
|
121
|
+
* agent actually sees when it fills the argument. `refused` is false for the
|
|
122
|
+
* inputs that may legitimately name a stray (a source to rescue, a file to
|
|
123
|
+
* remove).
|
|
124
|
+
*/
|
|
125
|
+
const wsPath = (kbDirName: string, what: string, refused = true): JsonSchema =>
|
|
126
|
+
str(
|
|
127
|
+
`${what}: starts with \`${kbDirName}/\` (e.g. \`${kbDirName}/KnowledgeBase/Foo.md\`).` +
|
|
128
|
+
(refused ? ' A path without that prefix is outside the repository and is refused.' : ''),
|
|
129
|
+
);
|
|
130
|
+
|
|
114
131
|
function asText(content: string | Buffer): string {
|
|
115
132
|
return typeof content === 'string' ? content : content.toString('utf8');
|
|
116
133
|
}
|
|
@@ -394,7 +411,7 @@ export function registerWorkspaceTools(
|
|
|
394
411
|
type: 'object',
|
|
395
412
|
properties: {
|
|
396
413
|
branch: BRANCH_INPUT,
|
|
397
|
-
path: str(
|
|
414
|
+
path: str(`Path to read, starting with \`${kbDirName}/\` (e.g. \`${kbDirName}/KnowledgeBase/Foo.md\`), or a \`__tool_chain_spill__/…\` ref from a truncated \`call_tool_chain\`.`),
|
|
398
415
|
offset: int('Start character index (default 0).'),
|
|
399
416
|
limit: int('Max characters to return from `offset`.'),
|
|
400
417
|
sessionId: SESSION_ID_INPUT,
|
|
@@ -453,13 +470,13 @@ export function registerWorkspaceTools(
|
|
|
453
470
|
mount({
|
|
454
471
|
name: 'list_files',
|
|
455
472
|
description:
|
|
456
|
-
|
|
473
|
+
`List a directory. Returns \`{ path, entries: [{ name, type, size? }] }\`. Omit \`path\` for the workspace root, which holds the repository as the \`${kbDirName}/\` folder: every content path starts with it (e.g. \`${kbDirName}/KnowledgeBase\`).` +
|
|
457
474
|
ONTOLOGY_BOUNDARY_NOTE,
|
|
458
475
|
inputs: {
|
|
459
476
|
type: 'object',
|
|
460
477
|
properties: {
|
|
461
478
|
branch: BRANCH_INPUT,
|
|
462
|
-
path: str(
|
|
479
|
+
path: str(`Directory to list, starting with \`${kbDirName}/\` (default: the workspace root, where the repository is the \`${kbDirName}/\` folder).`),
|
|
463
480
|
sessionId: SESSION_ID_INPUT,
|
|
464
481
|
},
|
|
465
482
|
required: ['branch'],
|
|
@@ -501,7 +518,7 @@ export function registerWorkspaceTools(
|
|
|
501
518
|
type: 'object',
|
|
502
519
|
properties: {
|
|
503
520
|
branch: BRANCH_INPUT,
|
|
504
|
-
path:
|
|
521
|
+
path: wsPath(kbDirName, 'Path'),
|
|
505
522
|
sessionId: SESSION_ID_INPUT,
|
|
506
523
|
},
|
|
507
524
|
required: ['branch', 'path'],
|
|
@@ -610,7 +627,7 @@ export function registerWorkspaceTools(
|
|
|
610
627
|
type: 'object',
|
|
611
628
|
properties: {
|
|
612
629
|
branch: BRANCH_INPUT,
|
|
613
|
-
path:
|
|
630
|
+
path: wsPath(kbDirName, 'Path'),
|
|
614
631
|
content: str('Full file content.'),
|
|
615
632
|
sessionId: SESSION_ID_INPUT,
|
|
616
633
|
},
|
|
@@ -657,7 +674,7 @@ export function registerWorkspaceTools(
|
|
|
657
674
|
description: 'Files to write; each created or overwritten.',
|
|
658
675
|
items: {
|
|
659
676
|
type: 'object',
|
|
660
|
-
properties: { path:
|
|
677
|
+
properties: { path: wsPath(kbDirName, 'Path'), content: str('Full file content.') },
|
|
661
678
|
required: ['path', 'content'],
|
|
662
679
|
additionalProperties: false,
|
|
663
680
|
},
|
|
@@ -705,7 +722,7 @@ export function registerWorkspaceTools(
|
|
|
705
722
|
type: 'object',
|
|
706
723
|
properties: {
|
|
707
724
|
branch: BRANCH_INPUT,
|
|
708
|
-
path:
|
|
725
|
+
path: wsPath(kbDirName, 'Path'),
|
|
709
726
|
old_string: str('Exact text to replace (include enough context to be unique).'),
|
|
710
727
|
new_string: str('Replacement text.'),
|
|
711
728
|
replace_all: { type: 'boolean', description: 'Replace every occurrence instead of requiring a unique match.' },
|
|
@@ -750,7 +767,7 @@ export function registerWorkspaceTools(
|
|
|
750
767
|
type: 'object',
|
|
751
768
|
properties: {
|
|
752
769
|
branch: BRANCH_INPUT,
|
|
753
|
-
path:
|
|
770
|
+
path: wsPath(kbDirName, 'Path to the file', false),
|
|
754
771
|
sessionId: SESSION_ID_INPUT,
|
|
755
772
|
},
|
|
756
773
|
required: ['branch', 'path'],
|
|
@@ -781,7 +798,7 @@ export function registerWorkspaceTools(
|
|
|
781
798
|
type: 'object',
|
|
782
799
|
properties: {
|
|
783
800
|
branch: BRANCH_INPUT,
|
|
784
|
-
path:
|
|
801
|
+
path: wsPath(kbDirName, 'Directory to create'),
|
|
785
802
|
sessionId: SESSION_ID_INPUT,
|
|
786
803
|
},
|
|
787
804
|
required: ['branch', 'path'],
|
|
@@ -808,8 +825,8 @@ export function registerWorkspaceTools(
|
|
|
808
825
|
type: 'object',
|
|
809
826
|
properties: {
|
|
810
827
|
branch: BRANCH_INPUT,
|
|
811
|
-
src:
|
|
812
|
-
dest:
|
|
828
|
+
src: wsPath(kbDirName, 'Source path', false),
|
|
829
|
+
dest: wsPath(kbDirName, 'Destination path'),
|
|
813
830
|
sessionId: SESSION_ID_INPUT,
|
|
814
831
|
},
|
|
815
832
|
required: ['branch', 'src', 'dest'],
|
|
@@ -842,8 +859,8 @@ export function registerWorkspaceTools(
|
|
|
842
859
|
type: 'object',
|
|
843
860
|
properties: {
|
|
844
861
|
branch: BRANCH_INPUT,
|
|
845
|
-
src:
|
|
846
|
-
dest:
|
|
862
|
+
src: wsPath(kbDirName, 'Source path', false),
|
|
863
|
+
dest: wsPath(kbDirName, 'Destination path'),
|
|
847
864
|
sessionId: SESSION_ID_INPUT,
|
|
848
865
|
},
|
|
849
866
|
required: ['branch', 'src', 'dest'],
|
|
@@ -877,8 +894,8 @@ export function registerWorkspaceTools(
|
|
|
877
894
|
type: 'object',
|
|
878
895
|
properties: {
|
|
879
896
|
branch: BRANCH_INPUT,
|
|
880
|
-
path:
|
|
881
|
-
destination:
|
|
897
|
+
path: wsPath(kbDirName, 'Path to the .zip archive', false),
|
|
898
|
+
destination: wsPath(kbDirName, "Directory to extract into (default: the zip's parent)"),
|
|
882
899
|
sessionId: SESSION_ID_INPUT,
|
|
883
900
|
},
|
|
884
901
|
required: ['branch', 'path'],
|
|
@@ -916,6 +933,9 @@ export function registerWorkspaceTools(
|
|
|
916
933
|
// extension policy applies per entry too, so a restricted run can't unzip a
|
|
917
934
|
// `.md` into the graph.
|
|
918
935
|
(wsRelPath) => {
|
|
936
|
+
// An entry that would land beside the repository is skipped with the
|
|
937
|
+
// corrected-path reason, like any other refused entry.
|
|
938
|
+
assertInsideRepo(wsRelPath, kbDirName);
|
|
919
939
|
writePolicy.assertPathWritable(ctx.sessionId, wsRelPath);
|
|
920
940
|
return assertOntologyWriteAllowed(sessionOntologyGate, ctx, wsRelPath);
|
|
921
941
|
},
|