borgmcp 2.0.9 → 2.0.10
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/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 +9 -9
- package/dist/index.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 +10 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +45 -5
- 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/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 +7 -7
- package/docs/LOCAL_SERVER.md +1 -1
- package/docs/RELEASING.md +11 -2
- 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 +17 -13
- package/src/regen-format.ts +12 -5
- package/src/regen.ts +2 -0
- package/src/remote-client.ts +60 -5
- package/src/roster-render.ts +58 -8
- package/src/runtime-metadata.ts +67 -0
- package/src/server-handshake.ts +7 -2
- package/src/working-repo.ts +30 -41
package/dist/working-repo.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export interface WorkingRepo {
|
|
9
9
|
name: string | null;
|
|
10
|
-
/** Canonical
|
|
10
|
+
/** Canonical public HTTPS identity, never a raw Git remote URL. */
|
|
11
11
|
origin: string | null;
|
|
12
|
+
state?: 'known' | 'unknown' | 'unavailable' | 'rejected';
|
|
12
13
|
}
|
|
13
14
|
export interface WorkingRepoDeps {
|
|
14
15
|
runGit?: (cwd: string, args: string[]) => {
|
|
@@ -17,13 +18,10 @@ export interface WorkingRepoDeps {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
* Convert a Git remote to
|
|
21
|
-
*
|
|
22
|
-
* URL userinfo, query strings, fragments, scheme, and SCP-style user prefixes
|
|
23
|
-
* are deliberately discarded. Inputs that cannot identify a host and path are
|
|
24
|
-
* treated as unreportable rather than forwarded verbatim.
|
|
21
|
+
* Convert a Git remote to the shared canonical public repository identity.
|
|
22
|
+
* Hostile or credential-bearing inputs are rejected rather than sanitized.
|
|
25
23
|
*/
|
|
26
|
-
export declare function canonicalizeWorkingRepoIdentity(origin: string):
|
|
24
|
+
export declare function canonicalizeWorkingRepoIdentity(origin: string): WorkingRepo | null;
|
|
27
25
|
/**
|
|
28
26
|
* Return a reportable identity for the caller's current directory.
|
|
29
27
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"working-repo.d.ts","sourceRoot":"","sources":["../src/working-repo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"working-repo.d.ts","sourceRoot":"","sources":["../src/working-repo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,mEAAmE;IACnE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,CAAC;CAC1D;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,KACX;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD;AAYD;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAWlF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,MAAsB,EAC3B,IAAI,GAAE,eAAoB,GACzB,WAAW,CA0Bb"}
|
package/dist/working-repo.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* included in lifecycle metadata.
|
|
7
7
|
*/
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { canonicalizeRepositoryIdentity } from 'borgmcp-shared/runtime-metadata';
|
|
9
10
|
function defaultRunGit(cwd, args) {
|
|
10
11
|
const result = spawnSync('git', args, { cwd, encoding: 'utf-8' });
|
|
11
12
|
return { status: result.status, stdout: result.stdout };
|
|
@@ -14,45 +15,20 @@ function trimmed(value) {
|
|
|
14
15
|
const normalized = value?.trim();
|
|
15
16
|
return normalized ? normalized : null;
|
|
16
17
|
}
|
|
17
|
-
function nameFromIdentity(identity) {
|
|
18
|
-
const lastPathSegment = identity.replace(/\/$/, '').split('/').pop();
|
|
19
|
-
const name = lastPathSegment?.replace(/\.git$/i, '').trim();
|
|
20
|
-
return name || null;
|
|
21
|
-
}
|
|
22
18
|
/**
|
|
23
|
-
* Convert a Git remote to
|
|
24
|
-
*
|
|
25
|
-
* URL userinfo, query strings, fragments, scheme, and SCP-style user prefixes
|
|
26
|
-
* are deliberately discarded. Inputs that cannot identify a host and path are
|
|
27
|
-
* treated as unreportable rather than forwarded verbatim.
|
|
19
|
+
* Convert a Git remote to the shared canonical public repository identity.
|
|
20
|
+
* Hostile or credential-bearing inputs are rejected rather than sanitized.
|
|
28
21
|
*/
|
|
29
22
|
export function canonicalizeWorkingRepoIdentity(origin) {
|
|
30
|
-
const raw = origin.trim();
|
|
31
|
-
if (!raw)
|
|
32
|
-
return null;
|
|
33
|
-
// Remote clients send this canonical form on subsequent lifecycle calls.
|
|
34
|
-
const canonical = raw.match(/^([A-Za-z0-9.-]+)\/([^?#\s]+)$/);
|
|
35
|
-
if (canonical) {
|
|
36
|
-
const host = canonical[1].toLowerCase();
|
|
37
|
-
const path = canonical[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
38
|
-
return host && path ? `${host}/${path}` : null;
|
|
39
|
-
}
|
|
40
23
|
try {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
24
|
+
const canonical = canonicalizeRepositoryIdentity(origin.trim());
|
|
25
|
+
return {
|
|
26
|
+
name: canonical.working_repo_name,
|
|
27
|
+
origin: canonical.working_repo_origin,
|
|
28
|
+
state: 'known',
|
|
29
|
+
};
|
|
46
30
|
}
|
|
47
31
|
catch {
|
|
48
|
-
// SCP-style SSH remote: discard its optional user prefix and URL-like
|
|
49
|
-
// query/fragment suffix before accepting only host + repository path.
|
|
50
|
-
const match = raw.match(/^(?:[^@\s/:]+@)?([A-Za-z0-9.-]+):\/?([^?#\s]+)(?:[?#].*)?$/);
|
|
51
|
-
if (match) {
|
|
52
|
-
const host = match[1].toLowerCase();
|
|
53
|
-
const path = match[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
|
|
54
|
-
return host && path ? `${host}/${path}` : null;
|
|
55
|
-
}
|
|
56
32
|
return null;
|
|
57
33
|
}
|
|
58
34
|
}
|
|
@@ -65,17 +41,31 @@ export function canonicalizeWorkingRepoIdentity(origin) {
|
|
|
65
41
|
*/
|
|
66
42
|
export function resolveWorkingRepo(cwd = process.cwd(), deps = {}) {
|
|
67
43
|
const runGit = deps.runGit ?? defaultRunGit;
|
|
68
|
-
|
|
44
|
+
let rootResult;
|
|
45
|
+
try {
|
|
46
|
+
rootResult = runGit(cwd, ['rev-parse', '--show-toplevel']);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { name: null, origin: null, state: 'unavailable' };
|
|
50
|
+
}
|
|
69
51
|
const root = rootResult.status === 0 ? trimmed(rootResult.stdout) : null;
|
|
70
52
|
if (!root) {
|
|
71
|
-
return { name: null, origin: null };
|
|
53
|
+
return { name: null, origin: null, state: 'unknown' };
|
|
54
|
+
}
|
|
55
|
+
let originResult;
|
|
56
|
+
try {
|
|
57
|
+
originResult = runGit(cwd, ['config', '--get', 'remote.origin.url']);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { name: null, origin: null, state: 'unavailable' };
|
|
72
61
|
}
|
|
73
|
-
const originResult = runGit(cwd, ['config', '--get', 'remote.origin.url']);
|
|
74
62
|
const originRaw = originResult.status === 0 ? trimmed(originResult.stdout) : null;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
if (!originRaw)
|
|
64
|
+
return { name: null, origin: null, state: 'unknown' };
|
|
65
|
+
return canonicalizeWorkingRepoIdentity(originRaw) ?? {
|
|
66
|
+
name: null,
|
|
67
|
+
origin: null,
|
|
68
|
+
state: 'rejected',
|
|
79
69
|
};
|
|
80
70
|
}
|
|
81
71
|
//# sourceMappingURL=working-repo.js.map
|
package/dist/working-repo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"working-repo.js","sourceRoot":"","sources":["../src/working-repo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"working-repo.js","sourceRoot":"","sources":["../src/working-repo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAejF,SAAS,aAAa,CAAC,GAAW,EAAE,IAAc;IAChD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IAClE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,OAAO,CAAC,KAAgC;IAC/C,MAAM,UAAU,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IACjC,OAAO,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,+BAA+B,CAAC,MAAc;IAC5D,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,8BAA8B,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAChE,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,iBAAiB;YACjC,MAAM,EAAE,SAAS,CAAC,mBAAmB;YACrC,KAAK,EAAE,OAAO;SACf,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,OAAO,CAAC,GAAG,EAAE,EAC3B,OAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC;IAC5C,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAC5D,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACxD,CAAC;IAED,IAAI,YAAY,CAAC;IACjB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAC5D,CAAC;IACD,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAClF,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACtE,OAAO,+BAA+B,CAAC,SAAS,CAAC,IAAI;QACnD,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,UAAU;KAClB,CAAC;AACJ,CAAC"}
|
|
@@ -8,7 +8,7 @@ The extraction copied the monorepo's `client/src/` production boundary and top-l
|
|
|
8
8
|
|
|
9
9
|
## Deliberate Transformations
|
|
10
10
|
|
|
11
|
-
- Replaced the monorepo dependency on `borgmcp-shared` with the exact audited registry release `borgmcp-shared@0.
|
|
11
|
+
- Replaced the monorepo dependency on `borgmcp-shared` with the exact audited registry release `borgmcp-shared@0.6.1` and a fresh standalone lockfile.
|
|
12
12
|
- Replaced local template, role-section, drone-address, and log high-water-mark implementations with `borgmcp-shared` exports.
|
|
13
13
|
- Removed monorepo-only website anti-drift tests and re-anchored remaining filesystem tests to this repository.
|
|
14
14
|
- Removed consumer lifecycle hooks, parent-directory deployment scripts, minification, and private integration-environment configuration.
|
|
@@ -21,8 +21,8 @@ The extraction copied the monorepo's `client/src/` production boundary and top-l
|
|
|
21
21
|
lightweight release tag failed before packaging. The reviewed `2.0.1`
|
|
22
22
|
recovery, `2.0.2`, `2.0.3`, `2.0.4`, `2.0.5`, and `2.0.6` successors were
|
|
23
23
|
published and registry-verified. The immutable `2.0.7` workflow failed before
|
|
24
|
-
package creation or npm publication. The `2.0.8`
|
|
25
|
-
registry-verified, so the next candidate identity is `2.0.
|
|
24
|
+
package creation or npm publication. The `2.0.8` and `2.0.9` successors were
|
|
25
|
+
published and registry-verified, so the next candidate identity is `2.0.10`. Extraction and
|
|
26
26
|
versioning do not authorize publication.
|
|
27
27
|
|
|
28
28
|
## Review Holds
|
|
@@ -38,9 +38,9 @@ artifact reaches no hosted authority.
|
|
|
38
38
|
Local enrollment now uses the reviewed client-generated credential/retry
|
|
39
39
|
contract, with a pre-request `PENDING` record in the local 0600-permission seat
|
|
40
40
|
store, exact-tuple ambiguous retry, and verified activation. The contract now
|
|
41
|
-
resolves to the audited registry `borgmcp-shared@0.
|
|
42
|
-
`borgmcp-server@0.1.
|
|
43
|
-
`borgmcp@2.0.
|
|
41
|
+
resolves to the audited registry `borgmcp-shared@0.6.1`. The matching
|
|
42
|
+
`borgmcp-server@0.1.17` release is published and registry-verified. Client
|
|
43
|
+
`borgmcp@2.0.9` is published and registry-verified. The immutable `v2.0.7`
|
|
44
44
|
attempt failed before publication and remains preserved. Publication of the next
|
|
45
|
-
candidate remains gated by reviewed `v2.0.
|
|
45
|
+
candidate remains gated by reviewed `v2.0.10` source, a fresh annotated tag, and
|
|
46
46
|
exact registry integrity and signature verification.
|
package/docs/LOCAL_SERVER.md
CHANGED
|
@@ -139,7 +139,7 @@ The default discovery endpoint is `https://127.0.0.1:7091`. Explicit `--host` va
|
|
|
139
139
|
|
|
140
140
|
## Release status
|
|
141
141
|
|
|
142
|
-
This self-hosted path consumes the published `borgmcp-shared@0.
|
|
142
|
+
This self-hosted path consumes the published `borgmcp-shared@0.6.1` v3 registry
|
|
143
143
|
release. The matching server owner-enrollment, cube-create, attach, restart, log,
|
|
144
144
|
and SSE implementation must also pass the full process-level local dogfood gate.
|
|
145
145
|
Until that gate opens the self-hosted path remains preview-only, and the client
|
package/docs/RELEASING.md
CHANGED
|
@@ -81,8 +81,17 @@ The annotated `v2.0.8` tag object
|
|
|
81
81
|
`8bc796d8fc4e307dca593138fb080984662c7d62`. Workflow run `30012098601`, attempt 1,
|
|
82
82
|
successfully published that exact source as `borgmcp@2.0.8`; the registry records integrity
|
|
83
83
|
`sha512-az1IKG4VNwAF/8PKEVK88gdRoFFBqgtl6ObXX+CcJadavNqkwJ9UFeP8LOJ2js911mIiXEm8c4xDf6MnO0GOng==`.
|
|
84
|
+
Never move, replace, reuse, or rerun that tag or workflow. Its successor used
|
|
85
|
+
the fresh `v2.0.9` identity from a reviewed protected-main commit and passed the
|
|
86
|
+
complete release gate.
|
|
87
|
+
|
|
88
|
+
The annotated `v2.0.9` tag object
|
|
89
|
+
`3a88bb4f46143789803a4e57f52b94d762f1ca9b` peels to protected-main commit
|
|
90
|
+
`18084fc486a041f3438f584a97d218c01a5e0399`. Workflow run `30047299013`, attempt 1,
|
|
91
|
+
successfully published that exact source as `borgmcp@2.0.9`; the registry records integrity
|
|
92
|
+
`sha512-lf0TZ8ZcpHv/Nt3LkY/IGxkUFkg3weavF+rLv6xLImDDhLIaZly1y8jUdo3UuheQVhrLnLcxoz37Myr4mPx9lg==`.
|
|
84
93
|
Never move, replace, reuse, or rerun that tag or workflow. The next candidate
|
|
85
|
-
uses the unused `v2.0.
|
|
94
|
+
uses the unused `v2.0.10` identity from a fresh reviewed protected-main commit
|
|
86
95
|
and requires the complete release gate again.
|
|
87
96
|
|
|
88
97
|
## Release Prerequisites
|
|
@@ -94,7 +103,7 @@ Before creating the release tag, independently verify all of these conditions:
|
|
|
94
103
|
- the extraction review confirms no private backend secrets, deployment
|
|
95
104
|
configuration, customer data, local state, or duplicated shared contracts
|
|
96
105
|
entered the public package;
|
|
97
|
-
- the exact audited registry dependency `borgmcp-shared@0.
|
|
106
|
+
- the exact audited registry dependency `borgmcp-shared@0.6.1` remains locked to
|
|
98
107
|
its canonical tarball and integrity;
|
|
99
108
|
- the client and matching server pass the complete local dogfood gate;
|
|
100
109
|
- the selected stable client version is unused and the exact release commit is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code, Codex, and OpenCode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
71
|
-
"borgmcp-shared": "0.
|
|
71
|
+
"borgmcp-shared": "0.6.1",
|
|
72
72
|
"chalk": "^5.3.0",
|
|
73
73
|
"prompts": "^2.4.2",
|
|
74
74
|
"which": "^4.0.0"
|
package/src/agent-runtime.ts
CHANGED
|
@@ -24,10 +24,17 @@ function isAgentKind(value: string | undefined): value is AgentKind {
|
|
|
24
24
|
* already-installed clients.
|
|
25
25
|
*/
|
|
26
26
|
export function resolveSessionAgentKind(env: NodeJS.ProcessEnv = process.env): AgentKind {
|
|
27
|
+
return resolveReportableSessionAgentKind(env) ?? 'claude';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolve only positively identified CLI state for advisory server reporting. */
|
|
31
|
+
export function resolveReportableSessionAgentKind(
|
|
32
|
+
env: NodeJS.ProcessEnv = process.env
|
|
33
|
+
): AgentKind | null {
|
|
27
34
|
if (isAgentKind(env[BORG_AGENT_KIND_ENV])) return env[BORG_AGENT_KIND_ENV];
|
|
28
35
|
if (env[BORG_OPENCODE_ENV] === '1') return 'opencode';
|
|
29
36
|
if (env[BORG_CODEX_REMOTE_WAKE_ENV] === '1') return 'codex';
|
|
30
|
-
return
|
|
37
|
+
return null;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
package/src/assimilate-cmd.ts
CHANGED
|
@@ -49,6 +49,7 @@ import type { ExpectedBinding, FinalizeServerSeatOutcome, PersistedLocalSeat } f
|
|
|
49
49
|
import type { SeatBinding, BindPendingSeatOutcome } from './seats.js';
|
|
50
50
|
import { createHash } from 'node:crypto';
|
|
51
51
|
import { buildOpenCodeLaunchArgs, type LaunchApprovalDecision } from './cli-tool-approval.js';
|
|
52
|
+
import { resolveWorkingRepo, type WorkingRepo } from './working-repo.js';
|
|
52
53
|
|
|
53
54
|
const PRIVATE_STATE_UNAVAILABLE_COPY = [
|
|
54
55
|
'Borg could not safely prepare its private local state.',
|
|
@@ -259,7 +260,7 @@ export interface AssimilateDeps {
|
|
|
259
260
|
assimilate: (
|
|
260
261
|
apiUrl: string,
|
|
261
262
|
token: string,
|
|
262
|
-
params: { cube_id: string; role_id: string; hostname?: string | null; prior_drone_id?: string; remint_invalid_prior?: boolean; model?: string | null; agent_kind?: 'claude' | 'codex' | 'opencode' | null; session_operation?: ServerSessionOperation; session_expected?: ExpectedBinding; revalidate_at_prepare?: boolean },
|
|
263
|
+
params: { cube_id: string; role_id: string; hostname?: string | null; prior_drone_id?: string; remint_invalid_prior?: boolean; model?: string | null; agent_kind?: 'claude' | 'codex' | 'opencode' | null; working_repo?: WorkingRepo; session_operation?: ServerSessionOperation; session_expected?: ExpectedBinding; revalidate_at_prepare?: boolean },
|
|
263
264
|
serverTrustIdentity?: string,
|
|
264
265
|
) => Promise<AssimilateResult>;
|
|
265
266
|
|
|
@@ -1183,6 +1184,7 @@ export async function runAssimilate(
|
|
|
1183
1184
|
hostname: deps.getHostname(),
|
|
1184
1185
|
agent_kind: cli,
|
|
1185
1186
|
model: effectiveModel,
|
|
1187
|
+
working_repo: resolveWorkingRepo(projectRoot),
|
|
1186
1188
|
...(reattachPriorId ? { prior_drone_id: reattachPriorId } : {}),
|
|
1187
1189
|
...(remintInvalidPrior ? { remint_invalid_prior: true } : {}),
|
|
1188
1190
|
session_operation: sessionOperation,
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { createInterface } from 'node:readline/promises';
|
|
|
16
16
|
import prompts from 'prompts';
|
|
17
17
|
import { readinessProbeEnv } from './readiness-probe.js';
|
|
18
18
|
import { resolveMcpBinaryPath } from './self-path.js';
|
|
19
|
+
import { buildRuntimeMetadataReport } from './runtime-metadata.js';
|
|
19
20
|
|
|
20
21
|
import type { AssimilateDeps } from './assimilate-cmd.js';
|
|
21
22
|
import {
|
|
@@ -322,10 +323,15 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
|
322
323
|
cubeId: params.cube_id,
|
|
323
324
|
roleId: params.role_id,
|
|
324
325
|
operation,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
...(params.prior_drone_id === undefined
|
|
327
|
+
? {}
|
|
328
|
+
: { priorDroneId: params.prior_drone_id }),
|
|
329
|
+
runtimeMetadata: buildRuntimeMetadataReport({
|
|
330
|
+
agentKind: params.agent_kind,
|
|
331
|
+
reportedModel: params.model,
|
|
332
|
+
workingRepo: params.working_repo,
|
|
333
|
+
}),
|
|
334
|
+
},
|
|
329
335
|
pending.credential,
|
|
330
336
|
{ fetchImpl: trust.fetchImpl },
|
|
331
337
|
);
|
package/src/index.ts
CHANGED
|
@@ -93,7 +93,11 @@ import {
|
|
|
93
93
|
formatWakePathPrefix,
|
|
94
94
|
shouldShowWakePathWarning,
|
|
95
95
|
} from './stream-status.js';
|
|
96
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
RUNTIME_METADATA_ADVISORY,
|
|
98
|
+
renderRoster,
|
|
99
|
+
renderRuntimeMetadataLines,
|
|
100
|
+
} from './roster-render.js';
|
|
97
101
|
import { resolveWorkingRepo } from './working-repo.js';
|
|
98
102
|
import {
|
|
99
103
|
DroneEvictedError,
|
|
@@ -110,6 +114,7 @@ import { initConsolePrefix, consolePrefix } from './console-prefix.js';
|
|
|
110
114
|
import {
|
|
111
115
|
resolveSessionAgentKind,
|
|
112
116
|
} from './codex-app-wake.js';
|
|
117
|
+
import { resolveReportableSessionAgentKind } from './agent-runtime.js';
|
|
113
118
|
import {
|
|
114
119
|
connectOpenCodeDrone,
|
|
115
120
|
injectOpenCodeEntry,
|
|
@@ -368,6 +373,7 @@ export async function main() {
|
|
|
368
373
|
const result = await regen(active.sessionToken, active.apiUrl, {
|
|
369
374
|
since,
|
|
370
375
|
reportedModel,
|
|
376
|
+
agentKind: resolveReportableSessionAgentKind(),
|
|
371
377
|
workingRepo: resolveWorkingRepo(),
|
|
372
378
|
serverTrustIdentity: active.serverTrustIdentity,
|
|
373
379
|
});
|
|
@@ -444,6 +450,7 @@ export async function main() {
|
|
|
444
450
|
// — an evicted/revoked seat FAILS server-side and is surfaced).
|
|
445
451
|
try {
|
|
446
452
|
const result = await regen(active!.sessionToken, active!.apiUrl, {
|
|
453
|
+
agentKind: resolveReportableSessionAgentKind(),
|
|
447
454
|
workingRepo: resolveWorkingRepo(),
|
|
448
455
|
serverTrustIdentity: active!.serverTrustIdentity,
|
|
449
456
|
});
|
|
@@ -1084,21 +1091,18 @@ export async function main() {
|
|
|
1084
1091
|
return { content: [{ type: 'text', text: 'No drones in this cube yet.' }] };
|
|
1085
1092
|
}
|
|
1086
1093
|
const rolesById = new Map(roles.map((r: any) => [r.id, r]));
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const wakeClass =
|
|
1094
|
+
const lines = drones.map((d: any) => {
|
|
1095
|
+
const r = rolesById.get(d.role_id) as any;
|
|
1096
|
+
const wakeClass =
|
|
1091
1097
|
d.wake_path_alert_class && d.wake_path_alert_class !== 'independent'
|
|
1092
1098
|
? ` — wake-path-class: ${d.wake_path_alert_class}`
|
|
1093
1099
|
: '';
|
|
1094
|
-
|
|
1095
|
-
?
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
});
|
|
1101
|
-
return { content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n${lines.join('\n')}` }] };
|
|
1100
|
+
return [
|
|
1101
|
+
`- **${d.label}** (id: ${d.id}) — Role: ${r?.name ?? '?'} (${d.role_id}) — last seen ${d.last_seen}${wakeClass}`,
|
|
1102
|
+
...renderRuntimeMetadataLines(d),
|
|
1103
|
+
].join('\n');
|
|
1104
|
+
});
|
|
1105
|
+
return { content: [{ type: 'text', text: `Drones in cube ${cubeId} (${drones.length}):\n\n_${RUNTIME_METADATA_ADVISORY}_\n\n${lines.join('\n')}` }] };
|
|
1102
1106
|
}
|
|
1103
1107
|
|
|
1104
1108
|
case 'borg_list-roles': {
|
package/src/regen-format.ts
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
} from 'borgmcp-shared/templates';
|
|
13
13
|
import { parseRoleSections } from 'borgmcp-shared/role-section';
|
|
14
14
|
import { formatDroneAddressToken } from 'borgmcp-shared/drone-address';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
RUNTIME_METADATA_ADVISORY,
|
|
17
|
+
renderRuntimeMetadataLines,
|
|
18
|
+
} from './roster-render.js';
|
|
16
19
|
import { shellEscape } from './shell-escape.js';
|
|
17
20
|
import { resolveInboxMonitorPath } from './self-path.js';
|
|
18
21
|
|
|
@@ -488,10 +491,12 @@ export function formatRegenMarkdown(
|
|
|
488
491
|
|
|
489
492
|
const droneOverview =
|
|
490
493
|
result.drones
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
494
|
+
.map((d: any) => {
|
|
495
|
+
const role = result.roles.find((r: any) => r.id === d.role_id);
|
|
496
|
+
return [
|
|
497
|
+
`- **${d.label}** (Role: ${role?.name ?? '?'}) — last seen ${humanAgo(new Date(d.last_seen))}`,
|
|
498
|
+
...renderRuntimeMetadataLines(d),
|
|
499
|
+
].join('\n');
|
|
495
500
|
})
|
|
496
501
|
.join('\n') || '_(no drones connected)_';
|
|
497
502
|
|
|
@@ -611,6 +616,8 @@ export function formatRegenMarkdown(
|
|
|
611
616
|
roleOverview,
|
|
612
617
|
'',
|
|
613
618
|
`## Connected drones`,
|
|
619
|
+
`_${RUNTIME_METADATA_ADVISORY}_`,
|
|
620
|
+
'',
|
|
614
621
|
droneOverview,
|
|
615
622
|
'',
|
|
616
623
|
`## Cube log`,
|
package/src/regen.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type AgentKind,
|
|
27
27
|
} from './regen-format.js';
|
|
28
28
|
import { resolveSessionAgentKind } from './codex-app-wake.js';
|
|
29
|
+
import { resolveReportableSessionAgentKind } from './agent-runtime.js';
|
|
29
30
|
import { handleVersionFlag } from './version.js';
|
|
30
31
|
import { gateAllowsActivation } from './launch-gate.js';
|
|
31
32
|
import { resolveWorkingRepo } from './working-repo.js';
|
|
@@ -85,6 +86,7 @@ async function main(): Promise<void> {
|
|
|
85
86
|
let result: Awaited<ReturnType<typeof regen>> | null = null;
|
|
86
87
|
try {
|
|
87
88
|
result = await regen(active.sessionToken, active.apiUrl, {
|
|
89
|
+
agentKind: resolveReportableSessionAgentKind(),
|
|
88
90
|
workingRepo: resolveWorkingRepo(),
|
|
89
91
|
serverTrustIdentity: active.serverTrustIdentity,
|
|
90
92
|
});
|
package/src/remote-client.ts
CHANGED
|
@@ -16,11 +16,14 @@ import {
|
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import {
|
|
18
18
|
createProtocolEnvelope,
|
|
19
|
+
decodeDroneRuntimeMetadataState,
|
|
19
20
|
decodeEvictDroneResult,
|
|
20
21
|
decodeProtocolEnvelope,
|
|
21
22
|
decodeProtocolErrorEnvelope,
|
|
22
23
|
decodeReassignDroneResult,
|
|
24
|
+
decodeUpdateDroneRuntimeMetadataResponse,
|
|
23
25
|
ErrorCode,
|
|
26
|
+
type AgentKind,
|
|
24
27
|
type EvictDroneResult,
|
|
25
28
|
type ReassignDroneResult,
|
|
26
29
|
} from 'borgmcp-shared/protocol';
|
|
@@ -33,6 +36,7 @@ import { getTemplate, type Template, type TemplateRole } from 'borgmcp-shared/te
|
|
|
33
36
|
import { parseRoleSections } from 'borgmcp-shared/role-section';
|
|
34
37
|
import type { FragmentView, NonClobberSyncResult } from './sync-roles-render.js';
|
|
35
38
|
import type { WorkingRepo } from './working-repo.js';
|
|
39
|
+
import { buildRuntimeMetadataPatch } from './runtime-metadata.js';
|
|
36
40
|
import { loadBorgServerTrust, type ServerFetch } from './server-trust.js';
|
|
37
41
|
import {
|
|
38
42
|
BorgServerError,
|
|
@@ -400,18 +404,42 @@ async function localCubeComposition(active: ActiveCube): Promise<{
|
|
|
400
404
|
if (!cubePayload || !rolePayload || !dronePayload) {
|
|
401
405
|
throw new Error('Local Borg server returned an incomplete cube response');
|
|
402
406
|
}
|
|
403
|
-
const
|
|
407
|
+
const drones = dronePayload.drones.map(withValidatedRuntimeMetadata);
|
|
408
|
+
const drone = drones.find((candidate) => candidate.id === active.droneId);
|
|
404
409
|
const role = rolePayload.roles.find((candidate) => candidate.id === drone?.role_id);
|
|
405
410
|
if (!drone || !role) throw new Error('Local Borg server no longer recognizes this drone seat');
|
|
406
411
|
return {
|
|
407
412
|
cube: cubePayload.cube,
|
|
408
413
|
roles: rolePayload.roles,
|
|
409
|
-
drones
|
|
414
|
+
drones,
|
|
410
415
|
role,
|
|
411
416
|
drone,
|
|
412
417
|
};
|
|
413
418
|
}
|
|
414
419
|
|
|
420
|
+
function withValidatedRuntimeMetadata<T extends Record<string, unknown>>(drone: T): T {
|
|
421
|
+
const state = decodeDroneRuntimeMetadataState(drone);
|
|
422
|
+
return {
|
|
423
|
+
...drone,
|
|
424
|
+
...state.runtime_metadata,
|
|
425
|
+
runtime_metadata_reported: state.runtime_metadata_reported,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function updateOwnRuntimeMetadata(
|
|
430
|
+
active: ActiveCube,
|
|
431
|
+
patch: ReturnType<typeof buildRuntimeMetadataPatch>,
|
|
432
|
+
): Promise<void> {
|
|
433
|
+
const payload = await localServerRequest<Record<string, unknown>>(
|
|
434
|
+
active,
|
|
435
|
+
`/api/cubes/${active.cubeId}/drones/self/metadata`,
|
|
436
|
+
'PATCH',
|
|
437
|
+
{ ...patch },
|
|
438
|
+
);
|
|
439
|
+
if (!payload) throw new Error('Local Borg server returned an empty runtime metadata response');
|
|
440
|
+
decodeUpdateDroneRuntimeMetadataResponse(payload);
|
|
441
|
+
}
|
|
442
|
+
|
|
415
443
|
function localCursorBinding(active: ActiveCube) {
|
|
416
444
|
return {
|
|
417
445
|
origin: active.apiUrl,
|
|
@@ -740,7 +768,7 @@ export async function whoami(
|
|
|
740
768
|
sessionToken: string,
|
|
741
769
|
apiUrl: string,
|
|
742
770
|
serverTrustIdentity?: string,
|
|
743
|
-
): Promise<{ cube_id: string; cube_name: string; drone_id: string; drone_label: string; role_id: string; role_name: string }> {
|
|
771
|
+
): Promise<{ cube_id: string; cube_name: string; drone_id: string; drone_label: string; role_id: string; role_name: string; runtime_metadata: { agent_kind: AgentKind | null; reported_model: string | null; working_repo_name: string | null; working_repo_origin: string | null }; runtime_metadata_reported: boolean }> {
|
|
744
772
|
const local = await localAuthorityContext(sessionToken, apiUrl, serverTrustIdentity);
|
|
745
773
|
const composed = await localCubeComposition(local);
|
|
746
774
|
return {
|
|
@@ -750,6 +778,13 @@ export async function whoami(
|
|
|
750
778
|
drone_label: composed.drone.label,
|
|
751
779
|
role_id: composed.role.id,
|
|
752
780
|
role_name: composed.role.name,
|
|
781
|
+
runtime_metadata: {
|
|
782
|
+
agent_kind: composed.drone.agent_kind,
|
|
783
|
+
reported_model: composed.drone.reported_model,
|
|
784
|
+
working_repo_name: composed.drone.working_repo_name,
|
|
785
|
+
working_repo_origin: composed.drone.working_repo_origin,
|
|
786
|
+
},
|
|
787
|
+
runtime_metadata_reported: composed.drone.runtime_metadata_reported,
|
|
753
788
|
};
|
|
754
789
|
}
|
|
755
790
|
|
|
@@ -786,7 +821,7 @@ export async function getRoster(
|
|
|
786
821
|
throw new Error('Local Borg server returned an incomplete roster response');
|
|
787
822
|
}
|
|
788
823
|
return {
|
|
789
|
-
drones: dronePayload.drones,
|
|
824
|
+
drones: dronePayload.drones.map(withValidatedRuntimeMetadata),
|
|
790
825
|
roles: rolePayload.roles,
|
|
791
826
|
message_taxonomy: cubePayload.cube.message_taxonomy ?? null,
|
|
792
827
|
since: dronePayload.since ?? since,
|
|
@@ -944,6 +979,8 @@ export async function regen(
|
|
|
944
979
|
since?: string;
|
|
945
980
|
/** Advisory self-report from the running agent; never model-routing config. */
|
|
946
981
|
reportedModel?: string;
|
|
982
|
+
/** Positively identified running Agent CLI; null means explicitly unknown. */
|
|
983
|
+
agentKind?: AgentKind | null;
|
|
947
984
|
/** Current cwd-derived identity; refreshed each regen to avoid stale routing data. */
|
|
948
985
|
workingRepo?: WorkingRepo;
|
|
949
986
|
/** Verified self-hosted authority from the caller's first active-state read. */
|
|
@@ -970,6 +1007,24 @@ export async function regen(
|
|
|
970
1007
|
apiUrl,
|
|
971
1008
|
opts.serverTrustIdentity,
|
|
972
1009
|
);
|
|
1010
|
+
if (
|
|
1011
|
+
opts.agentKind !== undefined ||
|
|
1012
|
+
opts.reportedModel !== undefined ||
|
|
1013
|
+
opts.workingRepo !== undefined
|
|
1014
|
+
) {
|
|
1015
|
+
const patch = buildRuntimeMetadataPatch({
|
|
1016
|
+
agentKind: opts.agentKind ?? null,
|
|
1017
|
+
reportedModel: opts.reportedModel,
|
|
1018
|
+
workingRepo: opts.workingRepo,
|
|
1019
|
+
});
|
|
1020
|
+
try {
|
|
1021
|
+
await updateOwnRuntimeMetadata(local, patch);
|
|
1022
|
+
} catch {
|
|
1023
|
+
// Metadata is advisory. Preserve the prior server value and continue with
|
|
1024
|
+
// the authenticated identity read; never echo rejected local input.
|
|
1025
|
+
console.warn('Local regen: runtime metadata update unavailable; preserving the prior safe report.');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
973
1028
|
const composed = await localCubeComposition(local);
|
|
974
1029
|
const cursor = opts.since === undefined
|
|
975
1030
|
? await getLocalServerCursor(localCursorBinding(local))
|
|
@@ -1494,7 +1549,7 @@ export async function getCube(cubeId: string, connection?: RemoteConnection): Pr
|
|
|
1494
1549
|
return {
|
|
1495
1550
|
...cubePayload.cube,
|
|
1496
1551
|
roles: rolePayload.roles,
|
|
1497
|
-
drones: dronePayload.drones,
|
|
1552
|
+
drones: dronePayload.drones.map(withValidatedRuntimeMetadata),
|
|
1498
1553
|
};
|
|
1499
1554
|
}
|
|
1500
1555
|
|