borgmcp 2.0.9 → 2.0.11
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/README.md +2 -2
- package/SECURITY.md +1 -1
- package/dist/agent-runtime.d.ts +2 -0
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +5 -1
- package/dist/agent-runtime.js.map +1 -1
- package/dist/assimilate-cmd.d.ts +2 -0
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +2 -0
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +6 -0
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -17
- package/dist/index.js.map +1 -1
- package/dist/local-manage-tool-result.d.ts +10 -0
- package/dist/local-manage-tool-result.d.ts.map +1 -1
- package/dist/local-manage-tool-result.js +36 -0
- package/dist/local-manage-tool-result.js.map +1 -1
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +6 -4
- package/dist/regen-format.js.map +1 -1
- package/dist/regen.js +2 -0
- package/dist/regen.js.map +1 -1
- package/dist/remote-client.d.ts +18 -6
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +99 -16
- package/dist/remote-client.js.map +1 -1
- package/dist/roster-render.d.ts +11 -0
- package/dist/roster-render.d.ts.map +1 -1
- package/dist/roster-render.js +41 -8
- package/dist/roster-render.js.map +1 -1
- package/dist/runtime-metadata.d.ts +13 -0
- package/dist/runtime-metadata.d.ts.map +1 -0
- package/dist/runtime-metadata.js +52 -0
- package/dist/runtime-metadata.js.map +1 -0
- package/dist/server-handshake.d.ts +2 -1
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +3 -0
- package/dist/server-handshake.js.map +1 -1
- package/dist/tool-manifest.js +2 -2
- package/dist/tool-manifest.js.map +1 -1
- package/dist/working-repo.d.ts +5 -7
- package/dist/working-repo.d.ts.map +1 -1
- package/dist/working-repo.js +30 -40
- package/dist/working-repo.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +8 -8
- package/docs/LOCAL_SERVER.md +1 -1
- package/docs/RELEASING.md +25 -13
- package/package.json +2 -2
- package/src/agent-runtime.ts +8 -1
- package/src/assimilate-cmd.ts +3 -1
- package/src/assimilate-deps.ts +10 -4
- package/src/index.ts +26 -21
- package/src/local-manage-tool-result.ts +47 -0
- package/src/regen-format.ts +12 -5
- package/src/regen.ts +2 -0
- package/src/remote-client.ts +124 -24
- package/src/roster-render.ts +58 -8
- package/src/runtime-metadata.ts +67 -0
- package/src/server-handshake.ts +7 -2
- package/src/tool-manifest.ts +2 -2
- package/src/working-repo.ts +30 -41
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentKind,
|
|
3
|
+
DroneRuntimeMetadata,
|
|
4
|
+
DroneRuntimeMetadataPatch,
|
|
5
|
+
} from 'borgmcp-shared/protocol';
|
|
6
|
+
import {
|
|
7
|
+
canonicalizeRepositoryIdentity,
|
|
8
|
+
validateReportedModel,
|
|
9
|
+
validateRuntimeMetadata,
|
|
10
|
+
validateRuntimeMetadataPatch,
|
|
11
|
+
} from 'borgmcp-shared/runtime-metadata';
|
|
12
|
+
import type { WorkingRepo } from './working-repo.js';
|
|
13
|
+
|
|
14
|
+
function safeReportedModel(value: string | null | undefined): string | null {
|
|
15
|
+
if (value == null) return null;
|
|
16
|
+
try {
|
|
17
|
+
return validateReportedModel(value);
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function reportableRepository(repo: WorkingRepo | undefined) {
|
|
24
|
+
if (!repo || repo.state === 'unavailable' || repo.state === 'rejected') return null;
|
|
25
|
+
if (repo.name === null && repo.origin === null) {
|
|
26
|
+
return { working_repo_name: null, working_repo_origin: null };
|
|
27
|
+
}
|
|
28
|
+
if (repo.name === null || repo.origin === null) return null;
|
|
29
|
+
try {
|
|
30
|
+
const canonical = canonicalizeRepositoryIdentity(repo.origin, repo.name);
|
|
31
|
+
return {
|
|
32
|
+
working_repo_name: canonical.working_repo_name,
|
|
33
|
+
working_repo_origin: canonical.working_repo_origin,
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildRuntimeMetadataReport(input: {
|
|
41
|
+
agentKind: AgentKind | null | undefined;
|
|
42
|
+
reportedModel?: string | null;
|
|
43
|
+
workingRepo?: WorkingRepo;
|
|
44
|
+
}): DroneRuntimeMetadata {
|
|
45
|
+
const repository = reportableRepository(input.workingRepo);
|
|
46
|
+
return validateRuntimeMetadata({
|
|
47
|
+
agent_kind: input.agentKind ?? null,
|
|
48
|
+
reported_model: safeReportedModel(input.reportedModel),
|
|
49
|
+
working_repo_name: repository?.working_repo_name ?? null,
|
|
50
|
+
working_repo_origin: repository?.working_repo_origin ?? null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildRuntimeMetadataPatch(input: {
|
|
55
|
+
agentKind: AgentKind | null;
|
|
56
|
+
reportedModel?: string;
|
|
57
|
+
workingRepo?: WorkingRepo;
|
|
58
|
+
}): DroneRuntimeMetadataPatch {
|
|
59
|
+
const patch: DroneRuntimeMetadataPatch = { agent_kind: input.agentKind };
|
|
60
|
+
if (input.reportedModel !== undefined) {
|
|
61
|
+
const model = safeReportedModel(input.reportedModel);
|
|
62
|
+
if (model !== null) patch.reported_model = model;
|
|
63
|
+
}
|
|
64
|
+
const repository = reportableRepository(input.workingRepo);
|
|
65
|
+
if (repository) Object.assign(patch, repository);
|
|
66
|
+
return validateRuntimeMetadataPatch(patch);
|
|
67
|
+
}
|
package/src/server-handshake.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
decodeProtocolTagPreflight,
|
|
16
16
|
ErrorCode,
|
|
17
17
|
type CreateCubeResponse,
|
|
18
|
+
type DroneRuntimeMetadata,
|
|
18
19
|
type ProtocolTagPreflight,
|
|
19
20
|
type ServerCapability,
|
|
20
21
|
} from 'borgmcp-shared/protocol';
|
|
@@ -235,6 +236,7 @@ export async function sendBorgServerAttach(
|
|
|
235
236
|
roleId: string;
|
|
236
237
|
operation: ServerSessionOperation;
|
|
237
238
|
priorDroneId?: string;
|
|
239
|
+
runtimeMetadata?: DroneRuntimeMetadata;
|
|
238
240
|
},
|
|
239
241
|
pendingBearer: string,
|
|
240
242
|
deps: {
|
|
@@ -274,9 +276,12 @@ export async function sendBorgServerAttach(
|
|
|
274
276
|
cube_id: request.cubeId,
|
|
275
277
|
role_id: request.roleId,
|
|
276
278
|
session_credential: pending.credential,
|
|
277
|
-
|
|
279
|
+
...(request.priorDroneId === undefined
|
|
280
|
+
? {}
|
|
281
|
+
: { prior_drone_id: request.priorDroneId }),
|
|
282
|
+
...(request.runtimeMetadata === undefined
|
|
278
283
|
? {}
|
|
279
|
-
: {
|
|
284
|
+
: { runtime_metadata: request.runtimeMetadata }),
|
|
280
285
|
})),
|
|
281
286
|
});
|
|
282
287
|
} catch (error) {
|
package/src/tool-manifest.ts
CHANGED
|
@@ -551,7 +551,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
551
551
|
{
|
|
552
552
|
name: 'borg_apply-template',
|
|
553
553
|
description:
|
|
554
|
-
'
|
|
554
|
+
'A client-orchestrated, non-clobbering application of a named template through role and taxonomy primitives. Requires the selected local client\'s live cube-manage grant. Roles are merged by name: new roles are created; existing template-named roles get missing sections/classes applied, while conflicting text is preserved. Operations are sequential, not atomic: if a later primitive fails, earlier operations may already be committed. Use borg_sync-roles to review and selectively accept conflicts.',
|
|
555
555
|
inputSchema: {
|
|
556
556
|
type: 'object',
|
|
557
557
|
properties: {
|
|
@@ -564,7 +564,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
564
564
|
{
|
|
565
565
|
name: 'borg_sync-roles',
|
|
566
566
|
description:
|
|
567
|
-
'
|
|
567
|
+
'A client-orchestrated, non-clobbering sync through role and taxonomy primitives. Requires the selected local client\'s live cube-manage grant. Dry-run (default) classifies each fragment as ADD, UNCHANGED, or CONFLICT. On apply, ADDs apply automatically; conflicts apply only through an explicit `decisions` accept, and unspecified conflicts remain unchanged. Operations are sequential, not atomic: if a later primitive fails, earlier operations may already be committed. Custom roles remain untouched.',
|
|
568
568
|
inputSchema: {
|
|
569
569
|
type: 'object',
|
|
570
570
|
properties: {
|
package/src/working-repo.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { canonicalizeRepositoryIdentity } from 'borgmcp-shared/runtime-metadata';
|
|
10
11
|
export interface WorkingRepo {
|
|
11
12
|
name: string | null;
|
|
12
|
-
/** Canonical
|
|
13
|
+
/** Canonical public HTTPS identity, never a raw Git remote URL. */
|
|
13
14
|
origin: string | null;
|
|
15
|
+
state?: 'known' | 'unknown' | 'unavailable' | 'rejected';
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface WorkingRepoDeps {
|
|
@@ -30,43 +32,19 @@ function trimmed(value: string | null | undefined): string | null {
|
|
|
30
32
|
return normalized ? normalized : null;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function nameFromIdentity(identity: string): string | null {
|
|
34
|
-
const lastPathSegment = identity.replace(/\/$/, '').split('/').pop();
|
|
35
|
-
const name = lastPathSegment?.replace(/\.git$/i, '').trim();
|
|
36
|
-
return name || null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
/**
|
|
40
|
-
* Convert a Git remote to
|
|
41
|
-
*
|
|
42
|
-
* URL userinfo, query strings, fragments, scheme, and SCP-style user prefixes
|
|
43
|
-
* are deliberately discarded. Inputs that cannot identify a host and path are
|
|
44
|
-
* treated as unreportable rather than forwarded verbatim.
|
|
36
|
+
* Convert a Git remote to the shared canonical public repository identity.
|
|
37
|
+
* Hostile or credential-bearing inputs are rejected rather than sanitized.
|
|
45
38
|
*/
|
|
46
|
-
export function canonicalizeWorkingRepoIdentity(origin: string):
|
|
47
|
-
const raw = origin.trim();
|
|
48
|
-
if (!raw) return null;
|
|
49
|
-
// Remote clients send this canonical form on subsequent lifecycle calls.
|
|
50
|
-
const canonical = raw.match(/^([A-Za-z0-9.-]+)\/([^?#\s]+)$/);
|
|
51
|
-
if (canonical) {
|
|
52
|
-
const host = canonical[1].toLowerCase();
|
|
53
|
-
const path = canonical[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
54
|
-
return host && path ? `${host}/${path}` : null;
|
|
55
|
-
}
|
|
39
|
+
export function canonicalizeWorkingRepoIdentity(origin: string): WorkingRepo | null {
|
|
56
40
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
const canonical = canonicalizeRepositoryIdentity(origin.trim());
|
|
42
|
+
return {
|
|
43
|
+
name: canonical.working_repo_name,
|
|
44
|
+
origin: canonical.working_repo_origin,
|
|
45
|
+
state: 'known',
|
|
46
|
+
};
|
|
61
47
|
} catch {
|
|
62
|
-
// SCP-style SSH remote: discard its optional user prefix and URL-like
|
|
63
|
-
// query/fragment suffix before accepting only host + repository path.
|
|
64
|
-
const match = raw.match(/^(?:[^@\s/:]+@)?([A-Za-z0-9.-]+):\/?([^?#\s]+)(?:[?#].*)?$/);
|
|
65
|
-
if (match) {
|
|
66
|
-
const host = match[1].toLowerCase();
|
|
67
|
-
const path = match[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
68
|
-
return host && path ? `${host}/${path}` : null;
|
|
69
|
-
}
|
|
70
48
|
return null;
|
|
71
49
|
}
|
|
72
50
|
}
|
|
@@ -83,17 +61,28 @@ export function resolveWorkingRepo(
|
|
|
83
61
|
deps: WorkingRepoDeps = {}
|
|
84
62
|
): WorkingRepo {
|
|
85
63
|
const runGit = deps.runGit ?? defaultRunGit;
|
|
86
|
-
|
|
64
|
+
let rootResult;
|
|
65
|
+
try {
|
|
66
|
+
rootResult = runGit(cwd, ['rev-parse', '--show-toplevel']);
|
|
67
|
+
} catch {
|
|
68
|
+
return { name: null, origin: null, state: 'unavailable' };
|
|
69
|
+
}
|
|
87
70
|
const root = rootResult.status === 0 ? trimmed(rootResult.stdout) : null;
|
|
88
71
|
if (!root) {
|
|
89
|
-
return { name: null, origin: null };
|
|
72
|
+
return { name: null, origin: null, state: 'unknown' };
|
|
90
73
|
}
|
|
91
74
|
|
|
92
|
-
|
|
75
|
+
let originResult;
|
|
76
|
+
try {
|
|
77
|
+
originResult = runGit(cwd, ['config', '--get', 'remote.origin.url']);
|
|
78
|
+
} catch {
|
|
79
|
+
return { name: null, origin: null, state: 'unavailable' };
|
|
80
|
+
}
|
|
93
81
|
const originRaw = originResult.status === 0 ? trimmed(originResult.stdout) : null;
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
name:
|
|
97
|
-
origin,
|
|
82
|
+
if (!originRaw) return { name: null, origin: null, state: 'unknown' };
|
|
83
|
+
return canonicalizeWorkingRepoIdentity(originRaw) ?? {
|
|
84
|
+
name: null,
|
|
85
|
+
origin: null,
|
|
86
|
+
state: 'rejected',
|
|
98
87
|
};
|
|
99
88
|
}
|