borgmcp 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/assimilate-cmd.d.ts +11 -2
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +60 -85
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +26 -8
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +7 -5
- package/dist/cli-help.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/regen-format.d.ts +1 -6
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +9 -8
- package/dist/regen-format.js.map +1 -1
- package/dist/remote-client.js +1 -1
- package/dist/remote-client.js.map +1 -1
- package/dist/repository-cube-init.d.ts +16 -1
- package/dist/repository-cube-init.d.ts.map +1 -1
- package/dist/repository-cube-init.js +167 -22
- package/dist/repository-cube-init.js.map +1 -1
- package/dist/server-errors.d.ts +11 -0
- package/dist/server-errors.d.ts.map +1 -1
- package/dist/server-errors.js +20 -0
- package/dist/server-errors.js.map +1 -1
- package/dist/server-facade.d.ts +2 -1
- package/dist/server-facade.d.ts.map +1 -1
- package/dist/server-facade.js +16 -10
- package/dist/server-facade.js.map +1 -1
- package/dist/server-handshake.d.ts +32 -2
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +161 -8
- package/dist/server-handshake.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +9 -7
- package/docs/LOCAL_SERVER.md +19 -13
- package/docs/RELEASING.md +21 -5
- package/package.json +2 -2
- package/src/assimilate-cmd.ts +85 -69
- package/src/assimilate-deps.ts +37 -7
- package/src/cli-help.ts +7 -5
- package/src/index.ts +5 -1
- package/src/regen-format.ts +11 -9
- package/src/remote-client.ts +1 -1
- package/src/repository-cube-init.ts +202 -23
- package/src/server-errors.ts +27 -0
- package/src/server-facade.ts +16 -10
- package/src/server-handshake.ts +226 -8
package/docs/LOCAL_SERVER.md
CHANGED
|
@@ -50,7 +50,7 @@ Options:
|
|
|
50
50
|
--enroll Prompt for a hidden enrollment invitation
|
|
51
51
|
--cube-name <name> Repository cube name (otherwise edit the proposed name)
|
|
52
52
|
--template software-dev|starter New-cube template (default: software-dev)
|
|
53
|
-
--yes, -y
|
|
53
|
+
--yes, -y Accept new-cube defaults; never adopt by name
|
|
54
54
|
--help, -h Show this help
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -91,14 +91,19 @@ exactly; the credential becomes active only after the versioned response is
|
|
|
91
91
|
decoded and the authenticated protocol handshake succeeds. A new process
|
|
92
92
|
resumes that pending enrollment before displaying another invitation prompt.
|
|
93
93
|
|
|
94
|
-
`borg assimilate` and `borg server cube init` share one
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
`borg assimilate` and `borg server cube init` share one repository-cube flow.
|
|
95
|
+
An existing local or server repository association is resolved without prompts.
|
|
96
|
+
Otherwise Borg checks accessible cubes against the exact proposed name. One
|
|
97
|
+
match displays the cube, repository, and server and requires explicit interactive
|
|
98
|
+
confirmation before the server atomically associates it; multiple matches fail
|
|
99
|
+
closed. `--yes` never adopts a cube by name. With no match, an authorized owner
|
|
100
|
+
continues to the creation guide, which shows the repository and server, proposes
|
|
101
|
+
an editable name, offers the Software Development and Starter templates, and
|
|
102
|
+
asks for one confirmation. `--cube-name <name>` and `--template
|
|
103
|
+
software-dev|starter` supply those creation values directly; `--yes` accepts the
|
|
104
|
+
repository default name and Software Development template. `borg server cube
|
|
105
|
+
init` stops after authoritative cube readback and never creates a drone. Bare
|
|
106
|
+
repositories fail closed.
|
|
102
107
|
|
|
103
108
|
For repositories with a canonical public `origin`, that origin is the stable
|
|
104
109
|
repository identity. Repositories without one receive an invisible UUID stored
|
|
@@ -168,8 +173,9 @@ The default discovery endpoint is `https://127.0.0.1:7091`. Explicit `--host` va
|
|
|
168
173
|
|
|
169
174
|
## Release status
|
|
170
175
|
|
|
171
|
-
This self-hosted path
|
|
172
|
-
|
|
173
|
-
and
|
|
174
|
-
|
|
176
|
+
This self-hosted path pins published `borgmcp-shared@0.6.4` and requires the
|
|
177
|
+
matching published `borgmcp-server@0.1.21` for protocol v5 repository resolve
|
|
178
|
+
and association. The server owner-enrollment, repository resolve/association,
|
|
179
|
+
cube-create, attach, restart, log, and SSE implementation must also pass the full
|
|
180
|
+
process-level local dogfood gate. Until that gate opens the self-hosted path remains preview-only, and the client
|
|
175
181
|
publish is deferred accordingly.
|
package/docs/RELEASING.md
CHANGED
|
@@ -104,8 +104,22 @@ The annotated `v2.0.11` tag object
|
|
|
104
104
|
`0676d93d3e8a64ba583e95f164d122b39687b53a`. Workflow run `30073844074`, attempt 1,
|
|
105
105
|
successfully published that exact source as `borgmcp@2.0.11`; the registry records integrity
|
|
106
106
|
`sha512-429njJ7HVmWhpm8E71RoLzG57Qehy3RSxEzs6fQofVoOIiZXnK4QQDwbQe9MPfhKifbvVN5lGcDKtKAjuC76ww==`.
|
|
107
|
+
Never move, replace, reuse, or rerun that tag or workflow.
|
|
108
|
+
|
|
109
|
+
The annotated `v2.1.0` tag object
|
|
110
|
+
`e0b189bb0dd1f1b12369ab81870113925e1afc41` peels to protected-main commit
|
|
111
|
+
`10f18e4010c9c518c0ea2184d42ed5388642a53f`. Workflow run `30166498938`, attempt 1,
|
|
112
|
+
successfully published that exact source as `borgmcp@2.1.0`; the same-run artifact report records integrity
|
|
113
|
+
`sha512-Gm9n69y3C0feNDYRPEOnShLCscQJ/BoylcRMH+sDDQJmdcbjY8+U6dDTcFv3w+uH5P/IVwJydB8TGRHPXL0FQA==`.
|
|
114
|
+
Never move, replace, reuse, or rerun that tag or workflow.
|
|
115
|
+
|
|
116
|
+
The annotated `v2.1.1` tag object
|
|
117
|
+
`aa8d772c4791b2da16d702d5d78e5b3c365796c4` peels to protected-main commit
|
|
118
|
+
`db0a6a76744928e0067eb03b8a236076c56ae758`. Workflow run `30174878044`, attempt 1,
|
|
119
|
+
successfully published that exact source as `borgmcp@2.1.1`; the same-run artifact report records integrity
|
|
120
|
+
`sha512-nZJ3i6LBIj/4743mqVRJTUH3YGmC8HfC/NuQb7eOv+/K/dyBogDnwZ3opiZiZSe4LE5CXuG72Sip7cPRoCoZrA==`.
|
|
107
121
|
Never move, replace, reuse, or rerun that tag or workflow. The next candidate
|
|
108
|
-
uses the unused `v2.
|
|
122
|
+
uses the unused `v2.2.0` identity from a fresh reviewed protected-main commit
|
|
109
123
|
and requires the complete release gate again.
|
|
110
124
|
|
|
111
125
|
## Release Prerequisites
|
|
@@ -117,10 +131,12 @@ Before creating the release tag, independently verify all of these conditions:
|
|
|
117
131
|
- the extraction review confirms no private backend secrets, deployment
|
|
118
132
|
configuration, customer data, local state, or duplicated shared contracts
|
|
119
133
|
entered the public package;
|
|
120
|
-
- the exact audited registry dependency `borgmcp-shared@0.6.
|
|
121
|
-
its canonical tarball and integrity
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
- the exact audited registry dependency `borgmcp-shared@0.6.4` remains locked to
|
|
135
|
+
its canonical tarball and integrity
|
|
136
|
+
`sha512-Wm4b0uoOAw9JCz5OTHD0Q2uXKkeWYdkVksdeZvRG8l62XGMY+G8GkNEsZT9L533LbVbQ29GhgF0htjDenQThDg==`;
|
|
137
|
+
- the client and matching published `borgmcp-server@0.1.21`, integrity
|
|
138
|
+
`sha512-g0QH4cKcsKuDyZs1MvBmvCoCLaZ4wVI04yHCGSvcAeWghWxSBH0IIF8Q5OFi1pLB7TZXOVP6D3QAabUQ2eJTFg==`, which pins
|
|
139
|
+
`borgmcp-shared@0.6.4`, pass the complete local dogfood gate;
|
|
124
140
|
- the selected stable client version is unused and the exact release commit is
|
|
125
141
|
on protected `main`;
|
|
126
142
|
- the repository and protected npm environment settings pass an operator audit;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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.6.
|
|
71
|
+
"borgmcp-shared": "0.6.4",
|
|
72
72
|
"chalk": "^5.3.0",
|
|
73
73
|
"prompts": "^2.4.2",
|
|
74
74
|
"which": "^4.0.0"
|
package/src/assimilate-cmd.ts
CHANGED
|
@@ -46,6 +46,9 @@ import {
|
|
|
46
46
|
CubeCreationConfirmationError,
|
|
47
47
|
CubeCreationOutcomeUnknownError,
|
|
48
48
|
LegacySessionCredentialCollisionError,
|
|
49
|
+
RepositoryAssociationOperationError,
|
|
50
|
+
RepositoryAssociationOutcomeUnknownError,
|
|
51
|
+
RepositoryAssociationResolutionError,
|
|
49
52
|
} from './server-errors.js';
|
|
50
53
|
import type { SeatStatus } from './seat-probe.js';
|
|
51
54
|
import type { ServerSessionOperation } from './config.js';
|
|
@@ -54,12 +57,18 @@ import type { SeatBinding, BindPendingSeatOutcome } from './seats.js';
|
|
|
54
57
|
import { createHash } from 'node:crypto';
|
|
55
58
|
import { buildOpenCodeLaunchArgs, type LaunchApprovalDecision } from './cli-tool-approval.js';
|
|
56
59
|
import { resolveWorkingRepo, type WorkingRepo } from './working-repo.js';
|
|
57
|
-
import type {
|
|
60
|
+
import type {
|
|
61
|
+
AssociateRepositoryCubeResponse,
|
|
62
|
+
CreateCubeRepository,
|
|
63
|
+
CubeTemplate,
|
|
64
|
+
} from 'borgmcp-shared/protocol';
|
|
58
65
|
import {
|
|
59
66
|
initializeRepositoryCube,
|
|
67
|
+
RepositoryAssociationConfirmationError,
|
|
60
68
|
RepositoryAssociationSaveError,
|
|
61
69
|
validRepositoryCubeName,
|
|
62
70
|
type RepositoryCubeCreation,
|
|
71
|
+
type RepositoryCubeResolution,
|
|
63
72
|
} from './repository-cube-init.js';
|
|
64
73
|
import type { GitRepositoryContext, RepositoryAssociation } from './repository-identity.js';
|
|
65
74
|
|
|
@@ -266,6 +275,18 @@ export interface AssimilateDeps {
|
|
|
266
275
|
serverCapabilities?: readonly string[];
|
|
267
276
|
} | null>;
|
|
268
277
|
|
|
278
|
+
resolveRepositoryCube: (
|
|
279
|
+
apiUrl: string,
|
|
280
|
+
token: string,
|
|
281
|
+
input: { workingRepoName: string; repository: CreateCubeRepository },
|
|
282
|
+
serverTrustIdentity?: string,
|
|
283
|
+
) => Promise<RepositoryCubeResolution>;
|
|
284
|
+
associateRepositoryCube: (
|
|
285
|
+
apiUrl: string,
|
|
286
|
+
token: string,
|
|
287
|
+
input: { cubeId: string; workingRepoName: string; repository: CreateCubeRepository },
|
|
288
|
+
serverTrustIdentity?: string,
|
|
289
|
+
) => Promise<AssociateRepositoryCubeResponse>;
|
|
269
290
|
listCubes: (apiUrl: string, token: string, serverTrustIdentity?: string) => Promise<CubeSummary[]>;
|
|
270
291
|
getCube: (apiUrl: string, token: string, cubeId: string, serverTrustIdentity?: string) => Promise<CubeDetail>;
|
|
271
292
|
createCube: (
|
|
@@ -684,71 +705,12 @@ export async function runAssimilate(
|
|
|
684
705
|
|
|
685
706
|
let initialized;
|
|
686
707
|
try {
|
|
687
|
-
if (!auth.serverCapabilities.includes('create_cube') && !existing) {
|
|
688
|
-
const repository = await deps.getRepositoryIdentity(repositoryContext);
|
|
689
|
-
const association = await deps.getRepositoryAssociation(auth.serverTrustIdentity, repository);
|
|
690
|
-
if (association) {
|
|
691
|
-
initialized = await initializeRepositoryCube({
|
|
692
|
-
mode, context: repositoryContext, serverOrigin: auth.apiUrl, flags: args.flags,
|
|
693
|
-
}, {
|
|
694
|
-
isTTY: deps.isTTY,
|
|
695
|
-
prompt: deps.prompt,
|
|
696
|
-
write: deps.stderr,
|
|
697
|
-
getIdentity: async () => repository,
|
|
698
|
-
getAssociation: async () => association,
|
|
699
|
-
saveAssociation: (identity, saved) =>
|
|
700
|
-
deps.saveRepositoryAssociation(auth.serverTrustIdentity, identity, saved),
|
|
701
|
-
getCube: (cubeId) => deps.getCube(auth.apiUrl, auth.token, cubeId, auth.serverTrustIdentity),
|
|
702
|
-
createCube: (params) => deps.createCube(auth.apiUrl, auth.token, params, auth.serverTrustIdentity),
|
|
703
|
-
});
|
|
704
|
-
} else {
|
|
705
|
-
const candidateName = args.flags.cubeName ?? repositoryContext.derivedName;
|
|
706
|
-
const accessible = await deps.listCubes(auth.apiUrl, auth.token, auth.serverTrustIdentity);
|
|
707
|
-
const matches = accessible.filter((cube) => cube.name === candidateName);
|
|
708
|
-
if (matches.length !== 1) {
|
|
709
|
-
deps.stderr(matches.length === 0
|
|
710
|
-
? `This enrolled client cannot create a cube on ${auth.apiUrl}. Ask the server operator to grant access to a cube, then rerun ${localAssimilateCommand(auth.apiUrl)}.\n`
|
|
711
|
-
: `More than one accessible cube is named '${candidateName}'. Nothing was changed. Ask the server operator for an unambiguous repository cube grant.\n`);
|
|
712
|
-
return 1;
|
|
713
|
-
}
|
|
714
|
-
const cube = await deps.getCube(auth.apiUrl, auth.token, matches[0].id, auth.serverTrustIdentity);
|
|
715
|
-
const association = {
|
|
716
|
-
cubeId: cube.id,
|
|
717
|
-
name: cube.name,
|
|
718
|
-
workingRepoName: repositoryContext.derivedName,
|
|
719
|
-
template: 'default' as const,
|
|
720
|
-
};
|
|
721
|
-
try {
|
|
722
|
-
await deps.saveRepositoryAssociation(auth.serverTrustIdentity, repository, association);
|
|
723
|
-
} catch {
|
|
724
|
-
throw new RepositoryAssociationSaveError();
|
|
725
|
-
}
|
|
726
|
-
const response = {
|
|
727
|
-
result: 'resolved' as const,
|
|
728
|
-
cube_id: cube.id,
|
|
729
|
-
name: cube.name,
|
|
730
|
-
working_repo_name: repositoryContext.derivedName,
|
|
731
|
-
repository,
|
|
732
|
-
template: 'default' as const,
|
|
733
|
-
human_seat_role_id: cube.roles.find((role) => role.is_human_seat)?.id ?? '',
|
|
734
|
-
default_worker_role_id: cube.roles.find((role) => role.is_default)?.id ?? '',
|
|
735
|
-
access: 'manage' as const,
|
|
736
|
-
};
|
|
737
|
-
deps.stderr(
|
|
738
|
-
`Cube already initialized.\n Name: ${cube.name}\n Template: Default (legacy)\n` +
|
|
739
|
-
` Repository: ${repositoryContext.root}\n Server: ${auth.apiUrl}\n` +
|
|
740
|
-
(mode === 'cube-init'
|
|
741
|
-
? `No drone was created.\nNext: borg assimilate --host ${shellEscape(auth.apiUrl)}\n`
|
|
742
|
-
: 'Continuing with role and seat setup...\n'),
|
|
743
|
-
);
|
|
744
|
-
initialized = { kind: 'success' as const, creation: { response, cube }, existing: true };
|
|
745
|
-
}
|
|
746
|
-
} else {
|
|
747
708
|
initialized = await initializeRepositoryCube({
|
|
748
709
|
mode,
|
|
749
710
|
context: repositoryContext,
|
|
750
711
|
serverOrigin: auth.apiUrl,
|
|
751
712
|
flags: args.flags,
|
|
713
|
+
canCreate: auth.serverCapabilities.includes('create_cube'),
|
|
752
714
|
}, {
|
|
753
715
|
isTTY: deps.isTTY,
|
|
754
716
|
prompt: deps.prompt,
|
|
@@ -757,11 +719,62 @@ export async function runAssimilate(
|
|
|
757
719
|
getAssociation: (repository) => deps.getRepositoryAssociation(auth.serverTrustIdentity, repository),
|
|
758
720
|
saveAssociation: (repository, association) =>
|
|
759
721
|
deps.saveRepositoryAssociation(auth.serverTrustIdentity, repository, association),
|
|
722
|
+
resolveAssociation: (repository, workingRepoName) => deps.resolveRepositoryCube(
|
|
723
|
+
auth.apiUrl,
|
|
724
|
+
auth.token,
|
|
725
|
+
{ repository, workingRepoName },
|
|
726
|
+
auth.serverTrustIdentity,
|
|
727
|
+
),
|
|
728
|
+
listCubes: () => deps.listCubes(auth.apiUrl, auth.token, auth.serverTrustIdentity),
|
|
729
|
+
associateCube: (params) => deps.associateRepositoryCube(
|
|
730
|
+
auth.apiUrl,
|
|
731
|
+
auth.token,
|
|
732
|
+
params,
|
|
733
|
+
auth.serverTrustIdentity,
|
|
734
|
+
),
|
|
760
735
|
getCube: (cubeId) => deps.getCube(auth.apiUrl, auth.token, cubeId, auth.serverTrustIdentity),
|
|
761
736
|
createCube: (params) => deps.createCube(auth.apiUrl, auth.token, params, auth.serverTrustIdentity),
|
|
762
737
|
});
|
|
763
|
-
}
|
|
764
738
|
} catch (error) {
|
|
739
|
+
if (error instanceof RepositoryAssociationOutcomeUnknownError) {
|
|
740
|
+
deps.stderr(
|
|
741
|
+
'Repository cube association outcome is unknown.\n' +
|
|
742
|
+
'The server may have created the repository binding; no local repository association was saved and no drone was created.\n' +
|
|
743
|
+
'Run the same command again; Borg will first resolve the authoritative server association without creating a cube.\n',
|
|
744
|
+
);
|
|
745
|
+
return 1;
|
|
746
|
+
}
|
|
747
|
+
if (error instanceof RepositoryAssociationResolutionError) {
|
|
748
|
+
deps.stderr(
|
|
749
|
+
'Repository cube association could not be resolved.\n' +
|
|
750
|
+
'Verify that the server is reachable and the client and server versions match, then run the same command again.\n' +
|
|
751
|
+
'No cube, repository binding, or drone was created.\n',
|
|
752
|
+
);
|
|
753
|
+
return 1;
|
|
754
|
+
}
|
|
755
|
+
if (error instanceof RepositoryAssociationOperationError) {
|
|
756
|
+
const recovery = error.failure === 'repository-already-associated'
|
|
757
|
+
? 'This repository is already associated with another cube. Run the same command again to use the existing managed association, or ask the server operator to correct the repository binding.'
|
|
758
|
+
: error.failure === 'cube-already-associated'
|
|
759
|
+
? 'The selected cube is already associated with another repository. Choose a different cube, or run the command from the repository already linked to that cube.'
|
|
760
|
+
: error.failure === 'access-denied'
|
|
761
|
+
? 'This enrolled client does not have permission to manage the selected cube. Ask the server operator to grant this client management access, then run the same command again.'
|
|
762
|
+
: 'The selected cube does not have valid authoritative roles. Ask the server operator to repair its role configuration, or choose another cube.';
|
|
763
|
+
deps.stderr(
|
|
764
|
+
'Repository cube association could not be completed.\n' +
|
|
765
|
+
`${recovery}\n` +
|
|
766
|
+
'No cube, repository binding, or drone was created.\n',
|
|
767
|
+
);
|
|
768
|
+
return 1;
|
|
769
|
+
}
|
|
770
|
+
if (error instanceof RepositoryAssociationConfirmationError) {
|
|
771
|
+
deps.stderr(
|
|
772
|
+
'Repository cube association could not be confirmed.\n' +
|
|
773
|
+
'The server may have created the repository binding; no local repository association was saved and no drone was created.\n' +
|
|
774
|
+
'Run the same command again; Borg will resolve authoritative server state before creating or associating a cube.\n',
|
|
775
|
+
);
|
|
776
|
+
return 1;
|
|
777
|
+
}
|
|
765
778
|
if (error instanceof RepositoryAssociationSaveError) {
|
|
766
779
|
deps.stderr(
|
|
767
780
|
'The repository cube was confirmed, but Borg could not save its local repository association.\n' +
|
|
@@ -772,18 +785,17 @@ export async function runAssimilate(
|
|
|
772
785
|
}
|
|
773
786
|
if (error instanceof CubeCreationOutcomeUnknownError) {
|
|
774
787
|
deps.stderr(
|
|
775
|
-
'Cube creation outcome is
|
|
776
|
-
'
|
|
777
|
-
'Run the same command again;
|
|
788
|
+
'Cube creation outcome is unconfirmed.\n' +
|
|
789
|
+
'The server may have created the cube and repository binding; no local repository association was saved and no drone was created.\n' +
|
|
790
|
+
'Run the same command again; Borg will resolve authoritative server state before creating a cube.\n',
|
|
778
791
|
);
|
|
779
792
|
return 1;
|
|
780
793
|
}
|
|
781
794
|
if (error instanceof CubeCreationConfirmationError) {
|
|
782
795
|
deps.stderr(
|
|
783
796
|
'Cube creation could not be confirmed.\n' +
|
|
784
|
-
|
|
785
|
-
'
|
|
786
|
-
'Resolve the conflict, then run the same command again.\n',
|
|
797
|
+
'The server may have created the cube and repository binding; no local repository association was saved and no drone was created.\n' +
|
|
798
|
+
'Run the same command again; Borg will resolve authoritative server state before creating a cube.\n',
|
|
787
799
|
);
|
|
788
800
|
return 1;
|
|
789
801
|
}
|
|
@@ -797,7 +809,11 @@ export async function runAssimilate(
|
|
|
797
809
|
if (error instanceof BorgServerError) {
|
|
798
810
|
return reportServerFailure(deps, auth.apiUrl, error);
|
|
799
811
|
}
|
|
800
|
-
deps.stderr(
|
|
812
|
+
deps.stderr(
|
|
813
|
+
'Repository cube initialization failed.\n' +
|
|
814
|
+
'The server may have created or associated a cube; local repository state may be incomplete and no drone was created.\n' +
|
|
815
|
+
'Run the same command again; Borg will resolve authoritative server state before creating or associating a cube.\n',
|
|
816
|
+
);
|
|
801
817
|
return 1;
|
|
802
818
|
}
|
|
803
819
|
if (initialized.kind === 'stop') return initialized.code;
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -25,10 +25,12 @@ import {
|
|
|
25
25
|
} from './remote-client.js';
|
|
26
26
|
import {
|
|
27
27
|
DEFAULT_LOCAL_SERVER_ORIGIN,
|
|
28
|
+
associateLocalBorgServerRepositoryCube,
|
|
28
29
|
connectLocalBorgServer,
|
|
29
30
|
createLocalBorgServerCube,
|
|
30
31
|
enrollLocalBorgServer,
|
|
31
32
|
probeLocalBorgServer,
|
|
33
|
+
resolveLocalBorgServerRepositoryCube,
|
|
32
34
|
resumeLocalBorgServerEnrollment,
|
|
33
35
|
sendBorgServerAttach,
|
|
34
36
|
} from './server-handshake.js';
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
} from './cubes.js';
|
|
46
48
|
import { loadBorgServerTrust } from './server-trust.js';
|
|
47
49
|
import { defaultProbeSeat } from './seat-probe.js';
|
|
48
|
-
import { BorgServerError } from './server-errors.js';
|
|
50
|
+
import { BorgServerError, CubeCreationConfirmationError } from './server-errors.js';
|
|
49
51
|
import {
|
|
50
52
|
findProjectRoot as cubesFindProjectRoot,
|
|
51
53
|
getActiveCube as cubesGetActive,
|
|
@@ -238,6 +240,29 @@ export function buildDefaultAssimilateDeps(
|
|
|
238
240
|
...(onPending === undefined ? {} : { onPending }),
|
|
239
241
|
}),
|
|
240
242
|
|
|
243
|
+
resolveRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
|
|
244
|
+
if (serverTrustIdentity === undefined) {
|
|
245
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
246
|
+
}
|
|
247
|
+
return resolveLocalBorgServerRepositoryCube(
|
|
248
|
+
apiUrl,
|
|
249
|
+
serverTrustIdentity,
|
|
250
|
+
token,
|
|
251
|
+
input,
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
associateRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
|
|
255
|
+
if (serverTrustIdentity === undefined) {
|
|
256
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
257
|
+
}
|
|
258
|
+
return associateLocalBorgServerRepositoryCube(
|
|
259
|
+
apiUrl,
|
|
260
|
+
serverTrustIdentity,
|
|
261
|
+
token,
|
|
262
|
+
input,
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
|
|
241
266
|
listCubes: async (apiUrl, token, serverTrustIdentity) => {
|
|
242
267
|
if (serverTrustIdentity === undefined) {
|
|
243
268
|
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
@@ -284,18 +309,23 @@ export function buildDefaultAssimilateDeps(
|
|
|
284
309
|
template: params.template,
|
|
285
310
|
},
|
|
286
311
|
);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
312
|
+
let cube;
|
|
313
|
+
try {
|
|
314
|
+
cube = await remoteGetCube(created.cube_id, {
|
|
315
|
+
apiUrl,
|
|
316
|
+
authToken: token,
|
|
317
|
+
serverTrustIdentity,
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
throw new CubeCreationConfirmationError('The server returned a cube result that could not be read back.');
|
|
321
|
+
}
|
|
292
322
|
if (
|
|
293
323
|
cube.id !== created.cube_id ||
|
|
294
324
|
cube.name !== created.name ||
|
|
295
325
|
!Array.isArray(cube.roles) ||
|
|
296
326
|
!cube.roles.some((role: { id?: string }) => role.id === created.default_worker_role_id)
|
|
297
327
|
) {
|
|
298
|
-
throw new
|
|
328
|
+
throw new CubeCreationConfirmationError('The server returned cube details outside the creation result.');
|
|
299
329
|
}
|
|
300
330
|
return {
|
|
301
331
|
response: created,
|
package/src/cli-help.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function serverHelpText(): string {
|
|
|
55
55
|
` status Report verified runtime evidence.\n` +
|
|
56
56
|
` update Verify and activate a local server artifact.\n` +
|
|
57
57
|
` invite Create a single-use invitation in an interactive terminal.\n` +
|
|
58
|
+
` dashboard View the running local server dashboard.\n` +
|
|
58
59
|
` cube init Initialize this Git repository's cube; does not create a drone.\n\n` +
|
|
59
60
|
`Run borg server <command> --help for server command options.\n`
|
|
60
61
|
);
|
|
@@ -71,7 +72,7 @@ export function cubeInitHelpText(): string {
|
|
|
71
72
|
` --enroll Prompt for a hidden enrollment invitation\n` +
|
|
72
73
|
` --cube-name <name> Repository cube name (otherwise edit the proposed name)\n` +
|
|
73
74
|
` --template software-dev|starter New-cube template (default: software-dev)\n` +
|
|
74
|
-
` --yes, -y
|
|
75
|
+
` --yes, -y Accept new-cube defaults; never adopt by name\n` +
|
|
75
76
|
` --help, -h Show this help\n`
|
|
76
77
|
);
|
|
77
78
|
}
|
|
@@ -101,11 +102,12 @@ export function assimilateHelpText(version: string): string {
|
|
|
101
102
|
` --no-template Unsupported for repository cube creation\n` +
|
|
102
103
|
` --cli claude|codex|opencode Agent CLI to launch (default: claude)\n` +
|
|
103
104
|
` --model claude:<model> Legacy Claude model override (configure models in the agent CLI)\n` +
|
|
104
|
-
` --yes, -y
|
|
105
|
+
` --yes, -y Accept new-cube defaults; never adopt by name\n\n` +
|
|
105
106
|
`Creation shows repository context, name, template, and one confirmation. An existing\n` +
|
|
106
|
-
`repository association skips all prompts.
|
|
107
|
-
`
|
|
108
|
-
`
|
|
107
|
+
`repository association skips all prompts. One accessible exact-name legacy cube requires\n` +
|
|
108
|
+
`explicit interactive adoption; ambiguous matches fail closed. An enrolled owner client may\n` +
|
|
109
|
+
`create an idempotent repository cube; ordinary clients require an explicit cube grant.\n` +
|
|
110
|
+
`Agent seats begin only after enrollment. Preview only.\n` +
|
|
109
111
|
`See docs/LOCAL_SERVER.md for self-hosted setup and current status.\n\n` +
|
|
110
112
|
`For local or provider-specific models, configure the selected agent CLI directly.\n` +
|
|
111
113
|
`OpenCode supports Ollama and other providers through its own model configuration.\n`
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
formatRegenMarkdown,
|
|
77
77
|
getDronePlaybook,
|
|
78
78
|
getDronePlaybookChapter,
|
|
79
|
+
markArrivalAnnouncedThisProcess,
|
|
79
80
|
nullTaxonomyTip,
|
|
80
81
|
regenWakePathDroneLabel,
|
|
81
82
|
} from './regen-format.js';
|
|
@@ -728,10 +729,12 @@ export async function main() {
|
|
|
728
729
|
if (!message || typeof message !== 'string') throw new Error('message is required');
|
|
729
730
|
const active = await getActiveCube();
|
|
730
731
|
if (!active) throw new Error('Not assimilated to a cube. Use borg_assimilate <cube-name> first.');
|
|
731
|
-
|
|
732
|
+
const lifecycleSignal = lifecycleSignalForMessage(message);
|
|
733
|
+
if (lifecycleSignal) {
|
|
732
734
|
const decision = await shouldSuppressLifecycleLog(active, message);
|
|
733
735
|
if (decision.suppress) {
|
|
734
736
|
await recordLifecycleLog(active, message);
|
|
737
|
+
if (lifecycleSignal === 'arrival') markArrivalAnnouncedThisProcess();
|
|
735
738
|
return {
|
|
736
739
|
content: [
|
|
737
740
|
{
|
|
@@ -760,6 +763,7 @@ export async function main() {
|
|
|
760
763
|
};
|
|
761
764
|
const result = await appendLog(active.sessionToken, active.apiUrl, message, appendOpts);
|
|
762
765
|
await recordLifecycleLog(active, message);
|
|
766
|
+
if (lifecycleSignal === 'arrival') markArrivalAnnouncedThisProcess();
|
|
763
767
|
const echo = result.routing?.message ? `\n${result.routing.message}` : '';
|
|
764
768
|
// gh#534: surface to the SENDER which directed recipients are
|
|
765
769
|
// currently unreachable via the wake path. The message is delivered
|
package/src/regen-format.ts
CHANGED
|
@@ -191,6 +191,12 @@ export function formatLeanOrientation(args: {
|
|
|
191
191
|
// formatLeanOrientation({ ..., source: 'clear' }), so the per-surface
|
|
192
192
|
// /clear block is gone (one place, not three).
|
|
193
193
|
|
|
194
|
+
let arrivalAnnouncedThisProcess = false;
|
|
195
|
+
|
|
196
|
+
export function markArrivalAnnouncedThisProcess(): void {
|
|
197
|
+
arrivalAnnouncedThisProcess = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
194
200
|
/**
|
|
195
201
|
* Build the universal drone playbook.
|
|
196
202
|
*
|
|
@@ -215,6 +221,9 @@ export function formatLeanOrientation(args: {
|
|
|
215
221
|
// about how Borg MCP works; this marker pins the param so the #490/#529 guard
|
|
216
222
|
// verifies borg_docs exposes `topic`.
|
|
217
223
|
export function getDronePlaybook(): string {
|
|
224
|
+
const arrivalInstruction = arrivalAnnouncedThisProcess
|
|
225
|
+
? ''
|
|
226
|
+
: '\n**When this MCP session first starts:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). After the post succeeds, the client suppresses this instruction until the MCP process restarts; an explicit `/mcp` reconnect may show it again.\n';
|
|
218
227
|
return `## How to operate as a Drone
|
|
219
228
|
|
|
220
229
|
You're a Drone in a Cube. Coordinate with other drones through the activity log.
|
|
@@ -242,8 +251,7 @@ You're a Drone in a Cube. Coordinate with other drones through the activity log.
|
|
|
242
251
|
5. Nothing actionable + no prompt → done; wait for next wake.
|
|
243
252
|
|
|
244
253
|
**On a \`<task-notification>\` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: \`borg_read-log unread_only=true limit=20\`, repeat until \`behind_by=0\`. Do NOT triage with \`since=<notification timestamp>\` (strict-after — skips the boundary entry) or a bare window (skips older-unread during bursts).
|
|
245
|
-
|
|
246
|
-
**On first wake this session:** post one \`ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>\` (run \`hostname\`; use cwd for the path). One-time per session — don't repeat on later wakes; skip if already posted this session (e.g. after a \`/mcp\` reconnect).
|
|
254
|
+
${arrivalInstruction}
|
|
247
255
|
|
|
248
256
|
**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct \`<your-label>:\` mention): call \`borg_ack entry_id=<id>\` within ~60s. Use the \`borg_ack\` TOOL, not an in-band \`ACK:\` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (\`STARTING\` / \`DONE\` still apply). Ack only routing-class signals — not every mention.
|
|
249
257
|
|
|
@@ -269,13 +277,6 @@ You're a Drone in a Cube. Coordinate with other drones through the activity log.
|
|
|
269
277
|
Any drone that commits code: run \`git diff --staged --stat\` before \`git commit\` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set).`;
|
|
270
278
|
}
|
|
271
279
|
|
|
272
|
-
/**
|
|
273
|
-
* Eager export of the playbook text. Cheap to compute (string concat);
|
|
274
|
-
* exporting as a constant lets callers splice it directly without a
|
|
275
|
-
* function call site.
|
|
276
|
-
*/
|
|
277
|
-
export const DRONE_PLAYBOOK = getDronePlaybook();
|
|
278
|
-
|
|
279
280
|
/**
|
|
280
281
|
* gh#912: the verbose operating-discipline DETAIL externalized out of the
|
|
281
282
|
* bootstrap regen into an on-demand chapter (fetched via the borg_playbook
|
|
@@ -387,6 +388,7 @@ let cachedRoleTextHash: string | null = null;
|
|
|
387
388
|
export function __resetRegenSessionState(): void {
|
|
388
389
|
boilerplateEmittedThisSession = false;
|
|
389
390
|
cachedRoleTextHash = null;
|
|
391
|
+
arrivalAnnouncedThisProcess = false;
|
|
390
392
|
}
|
|
391
393
|
|
|
392
394
|
function safetyDisciplinesForRole(detailedDescription: string | null | undefined): string[] {
|
package/src/remote-client.ts
CHANGED
|
@@ -712,7 +712,7 @@ async function authedFetch(
|
|
|
712
712
|
parsed.error !== null && typeof parsed.error === 'object' &&
|
|
713
713
|
parsed.error.code === ROLE_SECTION_CONFLICT_CODE
|
|
714
714
|
) {
|
|
715
|
-
//
|
|
715
|
+
// The shared protocol intentionally omits this server-local code. Re-validate the whole
|
|
716
716
|
// envelope through the strict shared decoder with only the recognized
|
|
717
717
|
// code substituted; no server-provided diagnostic is ever surfaced.
|
|
718
718
|
decodeProtocolErrorEnvelope({
|