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.
Files changed (55) hide show
  1. package/README.md +2 -2
  2. package/dist/agent-runtime.d.ts +2 -0
  3. package/dist/agent-runtime.d.ts.map +1 -1
  4. package/dist/agent-runtime.js +5 -1
  5. package/dist/agent-runtime.js.map +1 -1
  6. package/dist/assimilate-cmd.d.ts +2 -0
  7. package/dist/assimilate-cmd.d.ts.map +1 -1
  8. package/dist/assimilate-cmd.js +2 -0
  9. package/dist/assimilate-cmd.js.map +1 -1
  10. package/dist/assimilate-deps.d.ts.map +1 -1
  11. package/dist/assimilate-deps.js +6 -0
  12. package/dist/assimilate-deps.js.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +9 -9
  15. package/dist/index.js.map +1 -1
  16. package/dist/regen-format.d.ts.map +1 -1
  17. package/dist/regen-format.js +6 -4
  18. package/dist/regen-format.js.map +1 -1
  19. package/dist/regen.js +2 -0
  20. package/dist/regen.js.map +1 -1
  21. package/dist/remote-client.d.ts +10 -1
  22. package/dist/remote-client.d.ts.map +1 -1
  23. package/dist/remote-client.js +45 -5
  24. package/dist/remote-client.js.map +1 -1
  25. package/dist/roster-render.d.ts +11 -0
  26. package/dist/roster-render.d.ts.map +1 -1
  27. package/dist/roster-render.js +41 -8
  28. package/dist/roster-render.js.map +1 -1
  29. package/dist/runtime-metadata.d.ts +13 -0
  30. package/dist/runtime-metadata.d.ts.map +1 -0
  31. package/dist/runtime-metadata.js +52 -0
  32. package/dist/runtime-metadata.js.map +1 -0
  33. package/dist/server-handshake.d.ts +2 -1
  34. package/dist/server-handshake.d.ts.map +1 -1
  35. package/dist/server-handshake.js +3 -0
  36. package/dist/server-handshake.js.map +1 -1
  37. package/dist/working-repo.d.ts +5 -7
  38. package/dist/working-repo.d.ts.map +1 -1
  39. package/dist/working-repo.js +30 -40
  40. package/dist/working-repo.js.map +1 -1
  41. package/docs/EXTRACTION_PROVENANCE.md +7 -7
  42. package/docs/LOCAL_SERVER.md +1 -1
  43. package/docs/RELEASING.md +11 -2
  44. package/package.json +2 -2
  45. package/src/agent-runtime.ts +8 -1
  46. package/src/assimilate-cmd.ts +3 -1
  47. package/src/assimilate-deps.ts +10 -4
  48. package/src/index.ts +17 -13
  49. package/src/regen-format.ts +12 -5
  50. package/src/regen.ts +2 -0
  51. package/src/remote-client.ts +60 -5
  52. package/src/roster-render.ts +58 -8
  53. package/src/runtime-metadata.ts +67 -0
  54. package/src/server-handshake.ts +7 -2
  55. package/src/working-repo.ts +30 -41
@@ -7,8 +7,9 @@
7
7
  */
8
8
  export interface WorkingRepo {
9
9
  name: string | null;
10
- /** Canonical host/path identity, never a raw Git remote URL. */
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 a non-secret `host/org/repo` identity.
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): string | null;
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;AAGH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gEAAgE;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;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;AAkBD;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,MAAsB,EAC3B,IAAI,GAAE,eAAoB,GACzB,WAAW,CAeb"}
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"}
@@ -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 a non-secret `host/org/repo` identity.
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 url = new URL(origin);
42
- if (!['http:', 'https:', 'ssh:', 'git:'].includes(url.protocol))
43
- return null;
44
- const path = url.pathname.replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '');
45
- return url.hostname && path ? `${url.hostname.toLowerCase()}/${path}` : null;
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
- const rootResult = runGit(cwd, ['rev-parse', '--show-toplevel']);
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
- const origin = originRaw ? canonicalizeWorkingRepoIdentity(originRaw) : null;
76
- return {
77
- name: origin ? nameFromIdentity(origin) : null,
78
- origin,
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
@@ -1 +1 @@
1
- {"version":3,"file":"working-repo.js","sourceRoot":"","sources":["../src/working-repo.ts"],"names":[],"mappings":"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"}
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.5.1` and a fresh standalone lockfile.
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` successor was published and
25
- registry-verified, so the next candidate identity is `2.0.9`. Extraction and
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.5.1`. The matching
42
- `borgmcp-server@0.1.8` release is published and registry-verified. Client
43
- `borgmcp@2.0.8` is published and registry-verified. The immutable `v2.0.7`
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.9` source, a fresh annotated tag, and
45
+ candidate remains gated by reviewed `v2.0.10` source, a fresh annotated tag, and
46
46
  exact registry integrity and signature verification.
@@ -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.5.1` v3 registry
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.9` identity from a fresh reviewed protected-main commit
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.5.1` remains locked to
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.9",
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.5.1",
71
+ "borgmcp-shared": "0.6.1",
72
72
  "chalk": "^5.3.0",
73
73
  "prompts": "^2.4.2",
74
74
  "which": "^4.0.0"
@@ -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 'claude';
37
+ return null;
31
38
  }
32
39
 
33
40
  /**
@@ -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,
@@ -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
- ...(params.prior_drone_id === undefined
326
- ? {}
327
- : { priorDroneId: params.prior_drone_id }),
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 { formatRoleAgentLabel, formatWorkingRepoLabel, renderRoster } from './roster-render.js';
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
- const lines = drones.map((d: any) => {
1088
- const r = rolesById.get(d.role_id) as any;
1089
- const roleLabel = formatRoleAgentLabel(r?.name ?? '?', d.agent_kind);
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
- const reportedModel = d.reported_model
1095
- ? `Reported model: ${d.reported_model}`
1096
- : ' — Reported model: not reported';
1097
- const workingRepo = formatWorkingRepoLabel(d);
1098
- const workingRepoText = workingRepo ? ` — ${workingRepo}` : '';
1099
- return `- **${d.label}** (id: ${d.id}) role: ${roleLabel} (${d.role_id}) — last seen ${d.last_seen}${wakeClass}${reportedModel}${workingRepoText}`;
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': {
@@ -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 { formatRoleAgentLabel } from './roster-render.js';
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
- .map((d: any) => {
492
- const role = result.roles.find((r: any) => r.id === d.role_id);
493
- const roleLabel = formatRoleAgentLabel(role?.name ?? '?', d.agent_kind);
494
- return `- **${d.label}** (${roleLabel}) — last seen ${humanAgo(new Date(d.last_seen))}`;
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
  });
@@ -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 drone = dronePayload.drones.find((candidate) => candidate.id === active.droneId);
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: dronePayload.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